Skip to content

Commit a65f5e6

Browse files
authored
Report error responses as JSON (#90)
For element-hq/element-meta#2732 This changes the following: * Errors reported by the application now respond with a JSON body containing both an error describing the problem, and an errcode which is machine readable. * Admins may now (optionally) specify a errcode for each rejection condition. There is a docs/api.md to describe the change.
1 parent 642966b commit a65f5e6

File tree

8 files changed

+210
-107
lines changed

8 files changed

+210
-107
lines changed

README.md

+2-80
Original file line numberDiff line numberDiff line change
@@ -23,87 +23,9 @@ Optional parameters:
2323
It is possible to override the templates used to construct emails, and Github and Gitlab issues.
2424
See [templates/README.md](templates/README.md) for more information.
2525

26-
## HTTP endpoints
26+
## API Documentation
2727

28-
The following HTTP endpoints are exposed:
29-
30-
### GET `/api/listing/`
31-
32-
Serves submitted bug reports. Protected by basic HTTP auth using the
33-
username/password provided in the environment. A browsable list, collated by
34-
report submission date and time.
35-
36-
A whole directory can be downloaded as a tarball by appending the parameter `?format=tar.gz` to the end of the URL path
37-
38-
### POST `/api/submit`
39-
40-
Submission endpoint: this is where applications should send their reports.
41-
42-
The body of the request should be a multipart form-data submission, with the
43-
following form field names. (For backwards compatibility, it can also be a JSON
44-
object, but multipart is preferred as it allows more efficient transfer of the
45-
logs.)
46-
47-
* `text`: A textual description of the problem. Included in the
48-
`details.log.gz` file.
49-
50-
* `user_agent`: Application user-agent. Included in the `details.log.gz` file.
51-
52-
* `app`: Identifier for the application (eg 'riot-web'). Should correspond to a
53-
mapping configured in the configuration file for github issue reporting to
54-
work.
55-
56-
* `version`: Application version. Included in the `details.log.gz` file.
57-
58-
* `label`: Label to attach to the github issue, and include in the details file.
59-
60-
If using the JSON upload encoding, this should be encoded as a `labels` field,
61-
whose value should be a list of strings.
62-
63-
* `log`: a log file, with lines separated by newline characters. Multiple log
64-
files can be included by including several `log` parts.
65-
66-
If the log is uploaded with a filename `name.ext`, where `name` contains only
67-
alphanumerics, `.`, `-` or `_`, and `ext` is one of `log` or `txt`, then the
68-
file saved to disk is based on that. Otherwise, a suitable name is
69-
constructed.
70-
71-
If using the JSON upload encoding, the request object should instead include
72-
a single `logs` field, which is an array of objects with the following
73-
fields:
74-
75-
* `id`: textual identifier for the logs. Used as the filename, as above.
76-
* `lines`: log data. Newlines should be encoded as `\n`, as normal in JSON).
77-
78-
A summary of the current log file formats that are uploaded for `log` and
79-
`compressed-log` is [available](docs/submitted_reports.md).
80-
81-
* `compressed-log`: a gzipped logfile. Decompressed and then treated the same as
82-
`log`.
83-
84-
Compressed logs are not supported for the JSON upload encoding.
85-
86-
A summary of the current log file formats that are uploaded for `log` and
87-
`compressed-log` is [available](docs/submitted_reports.md).
88-
89-
* `file`: an arbitrary file to attach to the report. Saved as-is to disk, and
90-
a link is added to the github issue. The filename must be in the format
91-
`name.ext`, where `name` contains only alphanumerics, `-` or `_`, and `ext`
92-
is one of `jpg`, `png`, `txt`, `json`, `txt.gz` or `json.gz`.
93-
94-
Not supported for the JSON upload encoding.
95-
96-
* Any other form field names are interpreted as arbitrary name/value strings to
97-
include in the `details.log.gz` file.
98-
99-
If using the JSON upload encoding, this additional metadata should insted be
100-
encoded as a `data` field, whose value should be a JSON map. (Note that the
101-
values must be strings; numbers, objects and arrays will be rejected.)
102-
103-
The response (if successful) will be a JSON object with the following fields:
104-
105-
* `report_url`: A URL where the user can track their bug report. Omitted if
106-
issue submission was disabled.
28+
See [docs/api.md](docs/api.md) for more information.
10729

10830
## Notifications
10931

changelog.d/99.feature

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The `/api/submit` endpoint responds with JSON when it encounters an error.
2+
Please read the documentation in [docs/api.md](https://github.com/matrix-org/rageshake/blob/main/docs/api.md) to learn more.

docs/api.md

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
## HTTP endpoints
2+
3+
The following HTTP endpoints are exposed:
4+
5+
### GET `/api/listing/`
6+
7+
Serves submitted bug reports. Protected by basic HTTP auth using the
8+
username/password provided in the environment. A browsable list, collated by
9+
report submission date and time.
10+
11+
A whole directory can be downloaded as a tarball by appending the parameter `?format=tar.gz` to the end of the URL path
12+
13+
### POST `/api/submit`
14+
15+
Submission endpoint: this is where applications should send their reports.
16+
17+
The body of the request should be a multipart form-data submission, with the
18+
following form field names. (For backwards compatibility, it can also be a JSON
19+
object, but multipart is preferred as it allows more efficient transfer of the
20+
logs.)
21+
22+
* `text`: A textual description of the problem. Included in the
23+
`details.log.gz` file.
24+
25+
* `user_agent`: Application user-agent. Included in the `details.log.gz` file.
26+
27+
* `app`: Identifier for the application (eg 'riot-web'). Should correspond to a
28+
mapping configured in the configuration file for github issue reporting to
29+
work.
30+
31+
* `version`: Application version. Included in the `details.log.gz` file.
32+
33+
* `label`: Label to attach to the github issue, and include in the details file.
34+
35+
If using the JSON upload encoding, this should be encoded as a `labels` field,
36+
whose value should be a list of strings.
37+
38+
* `log`: a log file, with lines separated by newline characters. Multiple log
39+
files can be included by including several `log` parts.
40+
41+
If the log is uploaded with a filename `name.ext`, where `name` contains only
42+
alphanumerics, `.`, `-` or `_`, and `ext` is one of `log` or `txt`, then the
43+
file saved to disk is based on that. Otherwise, a suitable name is
44+
constructed.
45+
46+
If using the JSON upload encoding, the request object should instead include
47+
a single `logs` field, which is an array of objects with the following
48+
fields:
49+
50+
* `id`: textual identifier for the logs. Used as the filename, as above.
51+
* `lines`: log data. Newlines should be encoded as `\n`, as normal in JSON).
52+
53+
A summary of the current log file formats that are uploaded for `log` and
54+
`compressed-log` is [available](docs/submitted_reports.md).
55+
56+
* `compressed-log`: a gzipped logfile. Decompressed and then treated the same as
57+
`log`.
58+
59+
Compressed logs are not supported for the JSON upload encoding.
60+
61+
A summary of the current log file formats that are uploaded for `log` and
62+
`compressed-log` is [available](docs/submitted_reports.md).
63+
64+
* `file`: an arbitrary file to attach to the report. Saved as-is to disk, and
65+
a link is added to the github issue. The filename must be in the format
66+
`name.ext`, where `name` contains only alphanumerics, `-` or `_`, and `ext`
67+
is one of `jpg`, `png`, `txt`, `json`, `txt.gz` or `json.gz`.
68+
69+
Not supported for the JSON upload encoding.
70+
71+
* Any other form field names are interpreted as arbitrary name/value strings to
72+
include in the `details.log.gz` file.
73+
74+
If using the JSON upload encoding, this additional metadata should insted be
75+
encoded as a `data` field, whose value should be a JSON map. (Note that the
76+
values must be strings; numbers, objects and arrays will be rejected.)
77+
78+
The response (if successful) will be a JSON object with the following fields:
79+
80+
* `report_url`: A URL where the user can track their bug report. Omitted if
81+
issue submission was disabled.
82+
83+
## Error responses
84+
85+
The rageshake server will respond with a specific JSON payload when encountering an error.
86+
87+
```json
88+
{
89+
"error": "A human readable error string.",
90+
"errcode": "UNKNOWN",
91+
"policy_url": "https://github.com/matrix-org/rageshake/blob/master/docs/blocked_rageshake.md"
92+
}
93+
```
94+
95+
Where the fields are as follows:
96+
97+
- `error` is an error string to explain the error, in English.
98+
- `errcode` is a machine readable error code which can be used by clients to give a localized error.
99+
- `policy_url` is an optional URL that links to a reference document, which may be presented to users.
100+
101+
### Error codes
102+
103+
- `UNKNOWN` is a catch-all error when the appliation does not have a specific error.
104+
- `METHOD_NOT_ALLOWED` is reported when you have used the wrong method for an endpoint. E.g. GET instead of POST.
105+
- `DISALLOWED_APP` is reported when a report was rejected due to the report being sent from an unsupported
106+
app (see the `allowed_app_names` config option).
107+
- `BAD_HEADER` is reported when a header was not able to be parsed, such as `Content-Length`.
108+
- `CONTENT_TOO_LARGE` is reported when the reported content size is too large.
109+
- `BAD_CONTENT` is reported when the reports content could not be parsed.
110+
- `REJECTED` is reported when the submission could be understood but was rejected by `rejection_conditions`.
111+
This is the default value, see below for more information.
112+
113+
In addition to these error codes, the configuration allows application developers to specify specific error codes
114+
for report rejection under the `REJECTED_*` namespace. (see the `rejection_conditions` config option). Consult the
115+
administrator of your rageshake server in order to determine what error codes may be presented.

errors.go

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package main
2+
const (
3+
// ErrCodeBadContent is reported when the reports content could not be parsed.
4+
ErrCodeBadContent = "BAD_CONTENT";
5+
// ErrCodeBadHeader is reported when a header was not able to be parsed.
6+
ErrCodeBadHeader = "BAD_HEADER";
7+
// ErrCodeContentTooLarge is reported when the reported content size is too large.
8+
ErrCodeContentTooLarge = "CONTENT_TOO_LARGE";
9+
// ErrCodeDisallowedApp is reported when a report was rejected due to the report being sent from an unsupported
10+
ErrCodeDisallowedApp = "DISALLOWED_APP";
11+
// ErrCodeMethodNotAllowed is reported when you have used the wrong method for an endpoint.
12+
ErrCodeMethodNotAllowed = "METHOD_NOT_ALLOWED";
13+
// ErrCodeRejected is reported when the submission could be understood but was rejected by RejectionConditions.
14+
ErrCodeRejected = "REJECTED";
15+
// ErrCodeUnknown is a catch-all error when the appliation does not have a specific error.
16+
ErrCodeUnknown = "UNKNOWN";
17+
)

main.go

+26-9
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ var DefaultEmailBodyTemplate string
5454
var configPath = flag.String("config", "rageshake.yaml", "The path to the config file. For more information, see the config file in this repository.")
5555
var bindAddr = flag.String("listen", ":9110", "The port to listen on.")
5656

57+
// defaultErrorReason is the default reason string when not present for a rejection condition
58+
const defaultErrorReason string = "app or user text rejected"
59+
5760
type config struct {
5861
// Username and password required to access the bug report listings
5962
BugsUser string `yaml:"listings_auth_user"`
@@ -111,6 +114,8 @@ type RejectionCondition struct {
111114
UserTextMatch string `yaml:"usertext"`
112115
// Send this text to the client-side to inform the user why the server rejects the rageshake. Uses a default generic value if empty.
113116
Reason string `yaml:"reason"`
117+
// Send this text to the client-side to inform the user why the server rejects the rageshake. Uses a default error code REJECTED if empty.
118+
ErrorCode string `yaml:"errorcode"`
114119
}
115120

116121
func (c RejectionCondition) matchesApp(p *payload) bool {
@@ -148,26 +153,32 @@ func (c RejectionCondition) matchesUserText(p *payload) bool {
148153
return c.UserTextMatch == "" || regexp.MustCompile(c.UserTextMatch).MatchString(p.UserText)
149154
}
150155

151-
func (c RejectionCondition) shouldReject(p *payload) *string {
156+
// Returns a rejection reason and error code if the payload should be rejected by this condition, condition; otherwise returns `nil` for both results.
157+
func (c RejectionCondition) shouldReject(p *payload) (*string, *string) {
152158
if c.matchesApp(p) && c.matchesVersion(p) && c.matchesLabel(p) && c.matchesUserText(p) {
153159
// RejectionCondition matches all of the conditions: we should reject this submission/
154-
defaultReason := "app or user text rejected"
160+
var reason = defaultErrorReason
155161
if c.Reason != "" {
156-
return &c.Reason
162+
reason = c.Reason
163+
}
164+
var code = ErrCodeRejected
165+
if c.ErrorCode != "" {
166+
code = c.ErrorCode
157167
}
158-
return &defaultReason
168+
return &reason, &code
159169
}
160-
return nil
170+
return nil, nil
161171
}
162172

163-
func (c *config) matchesRejectionCondition(p *payload) *string {
173+
// Returns a rejection reason and error code if the payload should be rejected by any condition, condition; otherwise returns `nil` for both results.
174+
func (c *config) matchesRejectionCondition(p *payload) (*string, *string) {
164175
for _, rc := range c.RejectionConditions {
165-
reject := rc.shouldReject(p)
176+
reject, code := rc.shouldReject(p)
166177
if reject != nil {
167-
return reject
178+
return reject, code
168179
}
169180
}
170-
return nil
181+
return nil, nil
171182
}
172183

173184
func basicAuth(handler http.Handler, username, password, realm string) http.Handler {
@@ -341,5 +352,11 @@ func loadConfig(configPath string) (*config, error) {
341352
if err = yaml.Unmarshal(contents, &cfg); err != nil {
342353
return nil, err
343354
}
355+
356+
for idx, condition := range cfg.RejectionConditions {
357+
if condition.ErrorCode != "" && !strings.HasPrefix(condition.ErrorCode, "REJECTED_") {
358+
return nil, fmt.Errorf("Rejected condition %d was invalid. `errorcode` must be use the namespace REJECTED_", idx);
359+
}
360+
}
344361
return &cfg, nil
345362
}

0 commit comments

Comments
 (0)