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 +4 -4
- data/CHANGELOG.md +63 -21
- data/README.md +169 -6
- data/bin/hephaestus +20 -3
- data/lib/Rakefile +10 -0
- data/lib/hephaestus/engine.rb +72 -0
- data/lib/hephaestus/http.rb +36 -0
- data/lib/hephaestus/middleware/malformed_request.rb +100 -0
- data/lib/hephaestus/middleware/openapi_validation.rb +89 -0
- data/lib/hephaestus/middleware/tracing_attributes.rb +50 -0
- data/lib/hephaestus/middleware.rb +7 -0
- data/lib/hephaestus/support/hephaestus/api.rb +105 -0
- data/lib/hephaestus/support/hephaestus/webmocks/browser_webmock.rb +21 -0
- data/lib/hephaestus/support/hephaestus/webmocks/slack_webmock.rb +21 -0
- data/lib/hephaestus/support/hephaestus/webmocks/yetto_webmock.rb +195 -0
- data/lib/hephaestus/support/rails.rb +35 -0
- data/lib/hephaestus/test_helper.rb +45 -0
- data/lib/hephaestus/version.rb +2 -2
- data/lib/hephaestus.rb +1 -0
- data/lib/tasks/hephaestus_tasks.rake +7 -0
- data/lib/tasks/package.rake +9 -0
- data/lib/tasks/rubocop.rake +6 -0
- data/lib/version.rb +6 -0
- data/templates/app/controllers/app_controller.rb +1 -0
- data/templates/app/controllers/application_controller.rb +2 -101
- data/templates/app/jobs/update_yetto_job.rb +1 -1
- data/templates/app/lib/plug_app/middleware/openapi_validation.rb +3 -3
- data/templates/app/services/yetto_service.rb +2 -2
- data/templates/script/hmac_text +1 -1
- data/templates/test/jobs/update_yetto_job_test.rb +1 -1
- data/templates/test/support/webmocks/yetto_webmock.rb +5 -5
- metadata +330 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3f7de19c3d9ce15fc3a7851c0ca74e88202a62c8fcbf46cffb13131df112f384
|
4
|
+
data.tar.gz: bdd11f809c9c38d8c8b193a00c99ffce4a0dce847da1766a9f0ec371e932e359
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
60
|
+
|
61
|
+
- Remove excess :// characters from PROTOCOL by @balevine in https://github.com/yettoapp/hephaestus/pull/12
|
36
62
|
|
37
63
|
## New Contributors
|
38
|
-
|
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
|
-
|
58
|
-
|
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
|
-
|
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.
|
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
|
-
|
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
|
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
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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,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
|