Module: ActiveJob::Temporal::RailsEnvironmentLoader

Defined in:
lib/activejob/temporal/rails_environment_loader.rb

Defined Under Namespace

Classes: Error, Result

Constant Summary collapse

UNSAFE_WRITE_BITS =
0o022

Class Method Summary collapse

Class Method Details

.canonicalize_paths(*paths) ⇒ Object



82
83
84
# File 'lib/activejob/temporal/rails_environment_loader.rb', line 82

def canonicalize_paths(*paths)
  paths.to_h { |path| [path, File.realpath(path)] }
end

.eager_load_rails_applicationObject



128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/activejob/temporal/rails_environment_loader.rb', line 128

def eager_load_rails_application
  return unless Object.const_defined?(:Rails)

  rails = Object.const_get(:Rails)
  return unless rails.respond_to?(:application) && rails.respond_to?(:env)

  environment = rails.env
  return unless environment.respond_to?(:development?) && environment.respond_to?(:test?)
  return unless environment.development? || environment.test?

  application = rails.application
  application.eager_load! if application.respond_to?(:eager_load!)
end

.group_or_world_writable?(path) ⇒ Boolean

Returns:

  • (Boolean)


104
105
106
# File 'lib/activejob/temporal/rails_environment_loader.rb', line 104

def group_or_world_writable?(path)
  File.stat(path).mode.anybits?(UNSAFE_WRITE_BITS)
end

.load!(rails_root:, warning_io: $stderr, require_environment: Kernel.method(:require)) ⇒ Object



18
19
20
21
22
23
24
25
26
# File 'lib/activejob/temporal/rails_environment_loader.rb', line 18

def load!(rails_root:, warning_io: $stderr, require_environment: Kernel.method(:require))
  result = resolve(rails_root)
  result.warnings.each { |warning| warning_io.puts(warning) }
  return result unless result.loaded?

  require_environment.call(result.environment_path)
  eager_load_rails_application
  result
end

.missing_root_result(expanded_root, explicit_root) ⇒ Object

Raises:



71
72
73
74
75
# File 'lib/activejob/temporal/rails_environment_loader.rb', line 71

def missing_root_result(expanded_root, explicit_root)
  raise Error, "Cannot find Rails application at: #{expanded_root}" if explicit_root

  Result.new(loaded: false, rails_root: expanded_root, environment_path: nil, warnings: [])
end

.non_rails_result(canonical_root, rails_root, explicit_root) ⇒ Object



77
78
79
80
# File 'lib/activejob/temporal/rails_environment_loader.rb', line 77

def non_rails_result(canonical_root, rails_root, explicit_root)
  warnings = explicit_root ? non_rails_warnings(rails_root) : []
  Result.new(loaded: false, rails_root: canonical_root, environment_path: nil, warnings: warnings)
end

.non_rails_warnings(rails_root) ⇒ Object



121
122
123
124
125
126
# File 'lib/activejob/temporal/rails_environment_loader.rb', line 121

def non_rails_warnings(rails_root)
  [
    "Warning: #{rails_root} does not appear to be a Rails application",
    "Continuing without Rails environment. Job classes may not be available."
  ]
end

.owner_warnings(paths) ⇒ Object



108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/activejob/temporal/rails_environment_loader.rb', line 108

def owner_warnings(paths)
  current_uid = Process.uid
  unsafe_owner_path = paths.find do |path|
    owner_uid = File.stat(path).uid
    owner_uid != current_uid && !owner_uid.zero?
  end
  return [] unless unsafe_owner_path

  [
    "Warning: Rails environment path is not owned by the current user or root: #{unsafe_owner_path}"
  ]
end

.path_under_root?(canonical_root, path) ⇒ Boolean

Returns:

  • (Boolean)


100
101
102
# File 'lib/activejob/temporal/rails_environment_loader.rb', line 100

def path_under_root?(canonical_root, path)
  path == canonical_root || path.start_with?("#{canonical_root}#{File::SEPARATOR}")
end

.rails_paths(canonical_root) ⇒ Object



62
63
64
65
66
67
68
69
# File 'lib/activejob/temporal/rails_environment_loader.rb', line 62

def rails_paths(canonical_root)
  config_path = File.join(canonical_root, "config")
  {
    config: config_path,
    application: File.join(config_path, "application.rb"),
    environment: File.join(config_path, "environment.rb")
  }
end

.resolve(rails_root) ⇒ Object



28
29
30
31
32
33
34
35
36
# File 'lib/activejob/temporal/rails_environment_loader.rb', line 28

def resolve(rails_root)
  explicit_root = rails_root != "."
  expanded_root = File.expand_path(rails_root)
  return missing_root_result(expanded_root, explicit_root) unless File.directory?(expanded_root)

  resolve_existing_root(rails_root, explicit_root, File.realpath(expanded_root))
rescue Errno::EACCES => e
  raise Error, "Cannot inspect Rails application at: #{expanded_root} (#{e.message})"
end

.resolve_existing_root(rails_root, explicit_root, canonical_root) ⇒ Object

Raises:



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/activejob/temporal/rails_environment_loader.rb', line 38

def resolve_existing_root(rails_root, explicit_root, canonical_root)
  paths = rails_paths(canonical_root)
  return non_rails_result(canonical_root, rails_root, explicit_root) unless File.file?(paths.fetch(:application))

  environment_path = paths.fetch(:environment)
  raise Error, "Cannot find Rails environment at: #{environment_path}" unless File.file?(environment_path)

  canonical_paths = canonicalize_paths(
    canonical_root,
    paths.fetch(:config),
    paths.fetch(:application),
    environment_path
  )
  validate_paths_stay_under_root!(canonical_root, canonical_paths)
  validate_paths_not_writable_by_group_or_world!(canonical_paths)

  Result.new(
    loaded: true,
    rails_root: canonical_root,
    environment_path: canonical_paths.fetch(environment_path),
    warnings: owner_warnings(canonical_paths.values)
  )
end

.validate_paths_not_writable_by_group_or_world!(paths) ⇒ Object

Raises:



93
94
95
96
97
98
# File 'lib/activejob/temporal/rails_environment_loader.rb', line 93

def validate_paths_not_writable_by_group_or_world!(paths)
  unsafe_path = paths.values.find { |path| group_or_world_writable?(path) }
  return unless unsafe_path

  raise Error, "refusing to load Rails environment from group- or world-writable path: #{unsafe_path}"
end

.validate_paths_stay_under_root!(canonical_root, canonical_paths) ⇒ Object

Raises:



86
87
88
89
90
91
# File 'lib/activejob/temporal/rails_environment_loader.rb', line 86

def validate_paths_stay_under_root!(canonical_root, canonical_paths)
  escaped_path = canonical_paths.values.find { |path| !path_under_root?(canonical_root, path) }
  return unless escaped_path

  raise Error, "Refusing to load Rails environment path outside RAILS_ROOT: #{escaped_path}"
end