hephaestus 0.6.3 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 44ea48d1eb2074031506e9029eb3650657c2af45a4a0dc69d64297bcb3d1ce46
4
- data.tar.gz: 83098f6cc522540642f8607ddfc98816d47a1f68981a5414572800a3151ecaff
3
+ metadata.gz: 3f7de19c3d9ce15fc3a7851c0ca74e88202a62c8fcbf46cffb13131df112f384
4
+ data.tar.gz: bdd11f809c9c38d8c8b193a00c99ffce4a0dce847da1766a9f0ec371e932e359
5
5
  SHA512:
6
- metadata.gz: 7ff481188d3525a3b232099cd902db74cf2fa29bb368dc4e62fc6b4525bdbe9b978e08666a490fca443bbc76e7cbe73af65894e50d85e51f3ef9d4d3c144c66d
7
- data.tar.gz: 4fdc8e71109d913965923239ff10b00b185ee1289b34f503c196f25308b7aef8a5bf385b064c6931403b7bbcb1144057a297c0f4573c184fe7dcd200118bef34
6
+ metadata.gz: 19f8a930236eefd1fc9b6b6c0c612e314c103f5e2482aadf331543b8750b5b21df2de2b43c0b1dc02425eaaeba6cdd6f8f76e525626b94226e9eb85ac6cd6b16
7
+ data.tar.gz: 0cab0d8f9ae2fdeff83f5a85af55b65917d375c1937cdea374276c9005d6b471f3ae8b6c08e4e1c45181095722c51f13895d079628a77cc8061eb7e685fa358f
data/CHANGELOG.md CHANGED
@@ -1,83 +1,129 @@
1
+ # [v0.7.0] - 15-11-2024
2
+ ## What's Changed
3
+ * Run as an engine by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/25
4
+
5
+
6
+ **Full Changelog**: https://github.com/yettoapp/hephaestus/compare/v0.6.4...v0.7.0
7
+ # [v0.6.4] - 09-07-2024
8
+
9
+ ## What's Changed
10
+
11
+ - Updates for OpenAPI dependency by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/23
12
+
13
+ **Full Changelog**: https://github.com/yettoapp/hephaestus/compare/v0.6.3...v0.6.4
14
+
1
15
  # [v0.6.3] - 05-07-2024
16
+
2
17
  ## What's Changed
3
- * Changes to support Rack 3 param parsing by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/21
4
18
 
19
+ - Changes to support Rack 3 param parsing by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/21
5
20
 
6
21
  **Full Changelog**: https://github.com/yettoapp/hephaestus/compare/v0.6.2...v0.6.3
22
+
7
23
  # [v0.6.2] - 19-06-2024
24
+
8
25
  ## What's Changed
9
- * Use Ruby test suite by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/18
10
- * Catch up with plug updates by @balevine in https://github.com/yettoapp/hephaestus/pull/17
11
26
 
27
+ - Use Ruby test suite by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/18
28
+ - Catch up with plug updates by @balevine in https://github.com/yettoapp/hephaestus/pull/17
12
29
 
13
30
  **Full Changelog**: https://github.com/yettoapp/hephaestus/compare/v0.6.1...v0.6.2
31
+
14
32
  ## [v0.6.1] - 12-02-2024
33
+
15
34
  ## What's Changed
16
- * Port recent Ruby, TOML, and OpenAPI changes by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/16
17
35
 
36
+ - Port recent Ruby, TOML, and OpenAPI changes by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/16
18
37
 
19
38
  **Full Changelog**: https://github.com/yettoapp/hephaestus/compare/v0.6.0...v0.6.1
39
+
20
40
  ## [v0.6.0] - 19-12-2023
41
+
21
42
  ## What's Changed
22
- * Remove switch logic from plugs by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/15
23
43
 
44
+ - Remove switch logic from plugs by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/15
24
45
 
25
46
  **Full Changelog**: https://github.com/yettoapp/hephaestus/compare/v0.5.2...v0.6.0
47
+
26
48
  ## [v0.5.2] - 13-11-2023
49
+
27
50
  ## What's Changed
28
- * Fix RAILS_ENV in fly-staging.toml by @balevine in https://github.com/yettoapp/hephaestus/pull/13
29
- * Change dependabot checking to by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/14
30
51
 
52
+ - Fix RAILS_ENV in fly-staging.toml by @balevine in https://github.com/yettoapp/hephaestus/pull/13
53
+ - Change dependabot checking to by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/14
31
54
 
32
55
  **Full Changelog**: https://github.com/yettoapp/hephaestus/compare/v0.5.1...v0.5.2
56
+
33
57
  ## [v0.5.1] - 31-10-2023
58
+
34
59
  ## What's Changed
35
- * Remove excess :// characters from PROTOCOL by @balevine in https://github.com/yettoapp/hephaestus/pull/12
60
+
61
+ - Remove excess :// characters from PROTOCOL by @balevine in https://github.com/yettoapp/hephaestus/pull/12
36
62
 
37
63
  ## New Contributors
38
- * @balevine made their first contribution in https://github.com/yettoapp/hephaestus/pull/12
64
+
65
+ - @balevine made their first contribution in https://github.com/yettoapp/hephaestus/pull/12
39
66
 
40
67
  **Full Changelog**: https://github.com/yettoapp/hephaestus/compare/v0.5.0...v0.5.1
68
+
41
69
  ## [v0.5.0] - 13-10-2023
70
+
42
71
  ## What's Changed
43
- * More edits by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/11
44
72
 
73
+ - More edits by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/11
45
74
 
46
75
  **Full Changelog**: https://github.com/yettoapp/hephaestus/compare/v0.4.0...v0.5.0
76
+
47
77
  ## [v0.4.0] - 29-09-2023
78
+
48
79
  ## What's Changed
49
- * Yet more changes by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/10
50
80
 
81
+ - Yet more changes by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/10
51
82
 
52
83
  **Full Changelog**: https://github.com/yettoapp/hephaestus/compare/v0.3.1...v0.4.0
84
+
53
85
  ## [v0.3.1] - 18-09-2023
86
+
54
87
  **Full Changelog**: https://github.com/yettoapp/hephaestus/compare/v0.3.0...v0.3.1
88
+
55
89
  ## [v0.3.0] - 12-09-2023
90
+
56
91
  ## What's Changed
57
- * Bump actions/checkout from 3 to 4 by @dependabot in https://github.com/yettoapp/hephaestus/pull/8
58
- * Use centralized workflows for testing by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/9
92
+
93
+ - Bump actions/checkout from 3 to 4 by @dependabot in https://github.com/yettoapp/hephaestus/pull/8
94
+ - Use centralized workflows for testing by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/9
59
95
 
60
96
  ## New Contributors
61
- * @dependabot made their first contribution in https://github.com/yettoapp/hephaestus/pull/8
97
+
98
+ - @dependabot made their first contribution in https://github.com/yettoapp/hephaestus/pull/8
62
99
 
63
100
  **Full Changelog**: https://github.com/yettoapp/hephaestus/compare/v0.2.3...v0.3.0
101
+
64
102
  ## [v0.2.3] - 21-08-2023
103
+
65
104
  ## What's Changed
66
- * Updates from staging by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/7
67
105
 
106
+ - Updates from staging by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/7
68
107
 
69
108
  **Full Changelog**: https://github.com/yettoapp/hephaestus/compare/v0.2.2...v0.2.3
109
+
70
110
  ## [v0.2.2] - 21-08-2023
111
+
71
112
  ## What's Changed
72
- * 404/500 error handling by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/5
73
- * Parse OpenAPI schema *once* by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/6
74
113
 
114
+ - 404/500 error handling by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/5
115
+ - Parse OpenAPI schema _once_ by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/6
75
116
 
76
117
  **Full Changelog**: https://github.com/yettoapp/hephaestus/compare/v0.2.1...v0.2.2
118
+
77
119
  ## [v0.2.1] - 11-07-2023
120
+
78
121
  null
122
+
79
123
  ## [v0.2.0] - 11-07-2023
124
+
80
125
  null
126
+
81
127
  # Changelog
82
128
 
83
129
  ## [v0.1.3](https://github.com/yettoapp/hephaestus/tree/v0.1.3) (2023-03-15)
@@ -111,7 +157,3 @@ null
111
157
  **Merged pull requests:**
112
158
 
113
159
  - Add a Rails application template for plugs [\#1](https://github.com/yettoapp/hephaestus/pull/1) ([gjtorikian](https://github.com/gjtorikian))
114
-
115
-
116
-
117
- \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
data/README.md CHANGED
@@ -1,18 +1,29 @@
1
1
  # Hephaestus
2
2
 
3
- A plug template for Yetto. Use this to quickly spin up new plugs with a set of defaults.
3
+ A plug template for Yetto. It serves two purposes:
4
+
5
+ 1. Use this to quickly spin up new plugs with a set of defaults.
6
+ 2. Hephaestus also exists as an engine, and is responsible for the majority of a plug's Yetto communication logic.
4
7
 
5
8
  ## Usage
6
9
 
10
+ First, install Hephaestus _globally_, on your system:
11
+
7
12
  ```
8
13
  gem install hephaestus
14
+ ```
9
15
 
16
+ Then, in the parent directory where you want your plug to be created, run:
17
+
18
+ ```
10
19
  hephaestus plug-app
11
20
  ```
12
21
 
13
- Where `app` represents the name of the platform you'd like to interact with `jira`, `notion`, `slack`, etc.
22
+ Where `app` represents the name of the platform you'd like to interact with, like `jira`, `notion`, `slack`, etc.
14
23
 
15
- If you're working on updating/testing this gem locally, you may also want:
24
+ ### Testing locally
25
+
26
+ If you're working on updating/testing this gem locally, you may also try:
16
27
 
17
28
  ```
18
29
  rm -rf plug-app && DEBUG=1 hephaestus/bin/hephaestus plug-app
@@ -20,11 +31,145 @@ rm -rf plug-app && DEBUG=1 hephaestus/bin/hephaestus plug-app
20
31
 
21
32
  This way you can wipe the dir and quickly iterate on new changes.
22
33
 
23
- ## Building upon it
34
+ ## Building upon the base
35
+
36
+ Most of the files which Hephaestus creates for you shouldn't need much modification. The information below is part-informative, part-guidelines on how to extend the generated code for your specific plug.
37
+
38
+ ### Controllers
39
+
40
+ #### `ApplicationController`
41
+
42
+ Your application controller inherits from `Hephaestus::ApplicationController`, and handles the basics of request/response cycles. Two common callbacks to add here are:
43
+
44
+ - `before_action :set_request_span_context`, which lets you add data to the `OpenTelemetry` trace before a request occurs
45
+ - `after_action :set_response_span_context`, similarly, lets you add to the trace after the response is sent
46
+
47
+ For examples on how to integrate with these, check out `plug-email`.
48
+
49
+ #### `SettingsController`
50
+
51
+ Typically, your settings inherit directly from Hephaestus. We'll discuss more about how this works in `routes.rb`. Either way, you'll define two files:
52
+
53
+ - `app/views/settings/new.json.jbuilder`
54
+ - `app/views/settings/edit.json.jbuilder`
55
+
56
+ Define the settings UI in these files. See `plug-email` for an example of a settings page with no customizations.
57
+
58
+ However, for multi-step plugs, it's common to expect customized steps. If you'd like to extend the settings controller, create a file with one of two methods:
59
+
60
+ - `new`
61
+ - `edit`
62
+
63
+ You only need to define these methods if you're actually overriding the action—often, this only means `edit`. If you do define methods in this controller, remember to add `before_action :ensure_json_request` at the top. For examples on settings pages which have _some_ customization, see `plug-github` and `plug-linear`. For an example of a totally custom settings page, see `plug-zendesk`.
64
+
65
+ #### `AppController`
66
+
67
+ The logic to communicate with your platform sits in `AppController`. Every plug is unique, so the requirements for your platform varies!
68
+
69
+ At a minimum, though, you should place all the logic which authenticates requests in the `Authable` concern. See `plug-github` and `plug-linear` for practical examples.
70
+
71
+ #### `YettoController`
72
+
73
+ Place the logic which receives webhooks from Yetto in the `YettoController`. This controller must `include Hephaestus::ValidatesFromYetto`, and must define `before_action :from_yetto?`. Beyond that, you can respond to events as they come in, and fire jobs to perform the actions your platform needs to integrate with Yetto.
74
+
75
+ ### Jobs
76
+
77
+ Jobs are at the heart of plugs, because they perform time-sensitive work asynchronously. Since Hephaestus already creates an `ApplicationJob` which inherits from `Hephaestus::ApplicationJob`, there isn't much else you need to do, other than define your jobs.
78
+
79
+ Whenever you need to integrate with Yetto, just pass the appropriate arguments to `Hephaestus::UpdateYettoJob`.
80
+
81
+ The bulk of your plug logic should exist within these jobs. Keep the controllers as place to authenticate incoming requests and respond with appropriate status codes.
82
+
83
+ ### Constants
84
+
85
+ Place any and all constants in the file called `lib/constants.rb`.
86
+
87
+ Note that Hepheastus provides several convenience methods and constants for you. These are:
88
+
89
+ - `plug_shortname`: the downcased version of the plug (e.g., `Slack => slack`, `GitHub => github`)
90
+ - `plug_name`: The proper name of the plug (e.g., `Slack`, `GitHub`)
91
+ - `plug_url`: The plug's URL (e.g., `email{.plugs.yetto.app,.plugs.yetto.dev,.ngrok.io}`)
92
+
93
+ ### Application Configuration
94
+
95
+ The typical Rails' environment configs (`config/environments/{development.rb,test.rb,production.rb}`) are managed by Hephaestus. However, any plug can define their own environment files as well. These are added after Hephaestus' default configuration.
96
+
97
+ The `test.rb` file in `plug-email` provides an example of this.
98
+
99
+ #### Upgrading Rails
100
+
101
+ Upgrading Rails always introduces breaking changes, even between minor releases. To ensure that a plug still works after a Rails upgrade, first, add
102
+
103
+ ```ruby
104
+ rails "x"
105
+ ```
106
+
107
+ to your plug's Gemfile (where `"x"` is the new Rails version.) Note that this means plugs can run Rails versions independently of what Hephaestus provides (although variance is not recommended, it can help during the upgrade process).
108
+
109
+ Next, call `rails app:update:configs`. You can pretty much ignore every changed file, except the one that starts with `new_framework_defaults_`. Use these values to slowly reintroduce the new configuration changes, as described in [the Rails documentation](https://guides.rubyonrails.org/v8.0/upgrading_ruby_on_rails.html#configure-framework-defaults).
110
+
111
+ ### Integrating the Plug with the Engine
112
+
113
+ There are certain times where the child application needs to "inject" data into Hephaestus. These are typically customizations that are unique to the plug. Place these customizations in `config/application.rb`, inside of an `initializer :engines_blank_point do` block.
114
+
115
+ At a minimum, you'll need to define your plug's API version, and the version of the Yetto API it's communicating with:
116
+
117
+ ```ruby
118
+ initializer :engines_blank_point do
119
+ config.yetto_api_version = "2023-03-06"
120
+ config.plug_api_version = "2023-03-06"
121
+ end
122
+ ```
123
+
124
+ You can also define several other customizations:
125
+
126
+ - `config.tracing_body_filters`: use this to scrub out any sensitive data coming in from your platform, to prevent it from leaking in our metrics aggregator. See the override in `plug-email` for an example. The default is to simply log everything (after running it through [Rails' `ParameterFilter`](https://api.rubyonrails.org/v7.1/classes/ActiveSupport/ParameterFilter.html), of course), which may not be a good idea!
127
+ - `config.enhance_update_yetto_job`: use this to include any plug-specific logic which needs to occur as part of issuing calls to the Yetto API
128
+
129
+ For an example of both these customizations, see `plug-email`.
130
+
131
+ There's also middleware you can use to protect any endpoint with an OpenAPI schema. To do so, add:
132
+
133
+ ```ruby
134
+ require "hephaestus/middleware/openapi_validation"
135
+ config.middleware.use(Hephaestus::Middleware::OpenapiValidation, match_path: "/app/2023-03-06/path/", limit_methods_to: ["POST"], spec: Rails.root.join("lib/schemas/path/2023-03-06/openapi.json"))
136
+ ```
137
+
138
+ In this example, any `POST` to `match_path` is protected by the OpenAPI `spec` provided.
139
+
140
+ ### Services
141
+
142
+ Place any HTTP communication necessary to communicate with your platform in this directory. All other code should which communicates to your plug should rely on methods defined in this file.
143
+
144
+ See any of the plugs for an example of how to define this interaction, particularly with `httpsensible`.
145
+
146
+ ### Routes
147
+
148
+ If you do not have any custom actions to perform in the settings controller, define your settings pages as:
149
+
150
+ ```ruby
151
+ # you must always include this line when referring to `hephaestus_settings`
152
+ HephaestusSettingsController = Hephaestus::SettingsController
153
+
154
+ get "/api/#{Rails.application.config.plug_api_version}/settings", to: "hephaestus_settings#new"
155
+ get "/api/#{Rails.application.config.plug_api_version}/settings/:organization_id/:inbox_id/:plug_installation_id/edit", to: "hephaestus_settings#edit"
156
+ ```
157
+
158
+ Otherwise, be sure to refer to `hephaestus_settings` only for any default settings related actions:
159
+
160
+ ```ruby
161
+ # you must always include this line when referring to `hephaestus_settings`
162
+ HephaestusSettingsController = Hephaestus::SettingsController
163
+
164
+ get "/api/#{Rails.application.config.plug_api_version}/settings", to: "hephaestus_settings#new"
165
+ get "/api/#{Rails.application.config.plug_api_version}/settings/:organization_id/:inbox_id/:plug_installation_id/edit", to: "settings#edit"
166
+ ```
167
+
168
+ Otherwise, you can add any other routes your plug needs to `config/routes.rb`.
24
169
 
25
170
  ### Launching the server
26
171
 
27
- First, you'll note that you have a `script/ngrok` file, which launches an ngrok server at `https://plug-app.ngrok.io`, which maps locally to `http://localhost:6661`. This can be essential when testing the platform locally for the first time. (Keep in mind that you still need to run `script/server` to actually start the local server—this is just to help facilitate communication with the platform.)
172
+ First, you'll note that you have a `script/ngrok` file, which launches an ngrok server at `https://${hostname}-plug-app.ngrok.io`, which maps locally to `http://localhost:6661`. Setting up ngrok can be essential when testing the platform locally for the first time. (Keep in mind that you still need to run `script/server` to actually start the local server—this is just to help facilitate communication with the platform.)
28
173
 
29
174
  ### Setting up routes
30
175
 
@@ -42,6 +187,24 @@ After a user submits a plug installation on the Yetto side, it'll send a POST pa
42
187
 
43
188
  Any code which communicates with the third party should be placed in the `app/services` directory. A generic HTTP service is included.
44
189
 
190
+ ### Writing tests
191
+
192
+ In addition to writing tests for your plug interacting with its platform, many of your tests will also need to cover your plug's interaction with Yetto.
193
+
194
+ Writing these tests well comes with time; it's not an easy thing to cover in a pithy paragraph. Regardless, here's a bit of hopefully helpful advice.
195
+
196
+ Be sure to tests requests before _and_ after they occur. Typically, this takes the form of a `stub_request` and `assert_requested`. Similarly, be sure to:
197
+
198
+ - `include Hephaestus::Webmocks::SlackWebmock` whenever a plug action is expected to error out to Slack
199
+ - `include Hephaestus::Webmocks::YettoWebmock` whenever a plug needs to issue a request to Yetto
200
+
201
+ Use `include Hephaestus::API::TestHelpers` whenever you need to write a test which either
202
+
203
+ - match `"/#{plug_shortname}` routes (by issuing calls to `plug`). These are routes which your external server is calling into.
204
+ - match `/api` routes (by issuing calls to `api`). These are routes which Yetto is calling into.
205
+
206
+ For examples on when and how to use these methods, see `plug-github`.
207
+
45
208
  ## Acknowledgements
46
209
 
47
- This project was heavily based on [thoughtbot/suspenders](https://github.com/thoughtbot/suspenders).
210
+ The template generation for this project was heavily based on [thoughtbot/suspenders](https://github.com/thoughtbot/suspenders).
data/bin/hephaestus CHANGED
@@ -2,7 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "pathname"
5
- require 'rainbow'
5
+ require "rainbow"
6
6
 
7
7
  source_path = (Pathname.new(__FILE__).dirname + "../lib").expand_path
8
8
  $LOAD_PATH << source_path
@@ -34,7 +34,24 @@ if (str = ARGV.first)
34
34
  end
35
35
  end
36
36
 
37
- require "hephaestus"
37
+ puts Rainbow("Checking to see if Hephaestus is the latest and greatest...").green
38
+ require "net/http"
39
+ begin
40
+ Net::HTTP.get(URI("https://www.yetto.app"))
41
+ rescue Socket::ResolutionError
42
+ puts Rainbow("Unable to check for updates. Are you connected to the internet?").red
43
+ exit(1)
44
+ end
45
+
46
+ %x(gem outdated).split("\n").each do |line|
47
+ if line.include?("hephaestus")
48
+ puts Rainbow("There is a new version of Hephaestus available (`#{line}`). Please run 'gem update hephaestus' to update.").red
49
+ exit(1)
50
+ end
51
+ end
52
+
53
+ require "rails"
54
+ require "hephaestus/engine"
38
55
 
39
56
  if ARGV.empty?
40
57
  puts "Please provide a path for the new application"
@@ -57,7 +74,7 @@ unless path.start_with?("plug-")
57
74
  exit 1
58
75
  end
59
76
 
60
- unless path.length > "plug-".length
77
+ if path.split("-").length < 2
61
78
  puts Rainbow("\nError: The directory name must start with 'plug-'").red
62
79
  exit 1
63
80
  end
data/lib/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+
5
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
6
+ load "rails/tasks/engine.rake"
7
+
8
+ load "rails/tasks/statistics.rake"
9
+
10
+ require "bundler/gem_tasks"
@@ -0,0 +1,72 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ require "propshaft"
5
+ require "jbuilder"
6
+ require "solid_queue"
7
+ require "mission_control/jobs"
8
+
9
+ module Hephaestus
10
+ class Engine < ::Rails::Engine
11
+ isolate_namespace Hephaestus
12
+ config.generators.api_only = true
13
+
14
+ GIT_SHA = case Rails.env
15
+ when "production", "staging"
16
+ result = nil
17
+ result = "" if PRECOMPILING # skip if we're just precompiling assets
18
+
19
+ result ||= Rails.root.join("GIT_SHA").read if Rails.root.join("GIT_SHA").exist?
20
+
21
+ result ||= %x(git rev-parse HEAD) # fallback to git (should only happen locally)
22
+
23
+ result || ""
24
+ when "development"
25
+ %x(git rev-parse HEAD)
26
+ when "test"
27
+ "deadbeef"
28
+ end.chomp
29
+
30
+ require "hephaestus/http"
31
+
32
+ initializer "hephaestus.add_middleware" do |app|
33
+ Hephaestus::Engine.root.glob("lib/hephaestus/middleware/*.{rb}").each { |file| require_relative file }
34
+
35
+ app.config.middleware.insert(0, Hephaestus::Middleware::TracingAttributes)
36
+ app.config.middleware.insert(0, Hephaestus::Middleware::MalformedRequest)
37
+
38
+ app.config.middleware.use(Hephaestus::Middleware::OpenapiValidation)
39
+ end
40
+
41
+ initializer :append_migrations do |app|
42
+ unless app.root.to_s.match?(root.to_s)
43
+ config.paths["db/migrate"].expanded.each do |expanded_path|
44
+ app.config.paths["db/migrate"] << expanded_path
45
+ ActiveRecord::Migrator.migrations_paths << expanded_path
46
+ end
47
+ end
48
+ end
49
+
50
+ class << self
51
+ def insert_routes
52
+ Rails.application.routes.draw do
53
+ # Staff pages
54
+ get("staff", to: "staff#index")
55
+
56
+ constraints(->(request) { StaffController.staff_request?(request) }) do
57
+ mount(MissionControl::Jobs::Engine, at: "staff/jobs", as: :staff_jobs)
58
+ end
59
+
60
+ #############################################
61
+ # error pages -- these MUST be at the end! ##
62
+ #############################################
63
+
64
+ get("/500", to: "application#render500") if Rails.env.production? || Rails.env.staging?
65
+
66
+ match("/", to: "application#not_found", via: :all)
67
+ match("/*unmatched_route", to: "application#not_found", via: :all)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,36 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Hephaestus
5
+ module HTTP
6
+ OK = "OK"
7
+ OK_I = 200
8
+
9
+ CREATED = "Created"
10
+ CREATED_I = 201
11
+
12
+ NO_CONTENT = "No Content"
13
+ NO_CONTENT_I = 204
14
+
15
+ FOUND = "Found"
16
+ FOUND_I = 302
17
+
18
+ BAD_REQUEST = "Bad Request"
19
+ BAD_REQUEST_I = 400
20
+
21
+ UNAUTHORIZED = "Unauthorized"
22
+ UNAUTHORIZED_I = 401
23
+
24
+ FORBIDDEN = "Forbidden"
25
+ FORBIDDEN_I = 403
26
+
27
+ NOT_FOUND = "Not Found"
28
+ NOT_FOUND_I = 404
29
+
30
+ NOT_ACCEPTABLE = "Not Acceptable"
31
+ NOT_ACCEPTABLE_I = 406
32
+
33
+ SERVICE_UNAVAILABLE = "Service Unavailable"
34
+ SERVICE_UNAVAILABLE_I = 503
35
+ end
36
+ end
@@ -0,0 +1,100 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Hephaestus
5
+ module Middleware
6
+ # There is no valid reason for a request to contain a malformed string
7
+ # so just return HTTP 400 (Bad Request) if we receive one
8
+ class MalformedRequest
9
+ include ActionController::HttpAuthentication::Basic
10
+
11
+ NULL_BYTE_REGEX = Regexp.new(Regexp.escape("\u0000")).freeze
12
+
13
+ attr_reader :app
14
+
15
+ def initialize(app)
16
+ @app = app
17
+ end
18
+
19
+ def call(env)
20
+ return [Hephaestus::HTTP::BAD_REQUEST_I, { "Content-Type" => "text/plain" }, [Hephaestus::HTTP::BAD_REQUEST]] if request_contains_malformed_string?(env)
21
+
22
+ app.call(env)
23
+ end
24
+
25
+ private
26
+
27
+ def request_contains_malformed_string?(env)
28
+ # Duplicate the env, so it is not modified when accessing the parameters
29
+ # https://github.com/rails/rails/blob/34991a6ae2fc68347c01ea7382fa89004159e019/actionpack/lib/action_dispatch/http/parameters.rb#L59
30
+ request = ActionDispatch::Request.new(env.dup)
31
+
32
+ return true if malformed_path?(request.path)
33
+ return true if credentials_malformed?(request)
34
+
35
+ request.params.values.any? do |value|
36
+ param_has_null_byte?(value)
37
+ end
38
+ rescue ActionController::BadRequest
39
+ # If we can't build an ActionDispatch::Request something's wrong
40
+ # This would also happen if `#params` contains invalid UTF-8
41
+ # in this case we'll return a 400
42
+ true
43
+ end
44
+
45
+ def malformed_path?(path)
46
+ string_malformed?(Rack::Utils.unescape(path))
47
+ rescue ArgumentError
48
+ # Rack::Utils.unescape raised this, path is malformed.
49
+ true
50
+ end
51
+
52
+ def credentials_malformed?(request)
53
+ credentials = if has_basic_credentials?(request)
54
+ decode_credentials(request).presence
55
+ else
56
+ request.authorization.presence
57
+ end
58
+
59
+ return false unless credentials
60
+
61
+ string_malformed?(credentials)
62
+ end
63
+
64
+ def param_has_null_byte?(value, depth = 0)
65
+ # Guard against possible attack sending large amounts of nested params
66
+ # Should be safe as deeply nested params are highly uncommon.
67
+ return false if depth > 2
68
+
69
+ depth += 1
70
+
71
+ if value.respond_to?(:match)
72
+ string_malformed?(value)
73
+ elsif value.respond_to?(:values)
74
+ value.values.any? do |hash_value|
75
+ param_has_null_byte?(hash_value, depth)
76
+ end
77
+ elsif value.is_a?(Array)
78
+ value.any? do |array_value|
79
+ param_has_null_byte?(array_value, depth)
80
+ end
81
+ else
82
+ false
83
+ end
84
+ end
85
+
86
+ def string_malformed?(string)
87
+ # We're using match instead of include because that raises an ArgumentError
88
+ # when the string contains invalid UTF-8
89
+ #
90
+ # We try to encode the string from ASCII-8BIT to UTF8. If we failed to do
91
+ # so for certain characters in the string, those chars are probably incomplete
92
+ # multibyte characters.
93
+ string.dup.force_encoding(Encoding::UTF_8).match?(NULL_BYTE_REGEX)
94
+ rescue ArgumentError, Encoding::UndefinedConversionError
95
+ # If we're here, we caught a malformed string. Return true
96
+ true
97
+ end
98
+ end
99
+ end
100
+ end