diff --git a/api/http.yml b/api/http.yml index 7940f555..a511d149 100644 --- a/api/http.yml +++ b/api/http.yml @@ -6,6 +6,9 @@ info: paths: /channels/{id}/messages: post: + security: + - jwtAuth: [] + - basicAuth: [] summary: Sends message to the communication channel description: | Sends message to the communication channel. Messages can be sent as @@ -13,22 +16,21 @@ paths: tags: - messages parameters: - - $ref: "#/components/parameters/Authorization" - $ref: "#/components/parameters/ID" requestBody: $ref: "#/components/requestBodies/MessageReq" responses: - '202': + "202": description: Message is accepted for processing. - '400': + "400": description: Message discarded due to its malformed content. - '403': + "403": description: Message discarded due to missing or invalid credentials. - '404': + "404": description: Message discarded due to invalid channel id. - '415': + "415": description: Message discarded due to invalid or missing content type. - '500': + "500": description: Unexpected server-side error occurred. components: @@ -94,16 +96,15 @@ components: type: array items: $ref: "#/components/schemas/SenMLRecord" - - parameters: - Authorization: - name: Authorization - description: Access token. + securitySchemes: + basicAuth: + type: http + scheme: basic + jwtAuth: + type: apiKey in: header - schema: - type: string - format: jwt - required: true + name: Authorization + parameters: ID: name: id description: Unique channel identifier. @@ -116,11 +117,11 @@ components: requestBodies: MessageReq: description: | - Message to be distributed. Since the platform expects messages to be - properly formatted SenML in order to be post-processed, clients are - obliged to specify Content-Type header for each published message. - Note that all messages that aren't SenML will be accepted and published, - but no post-processing will be applied. + Message to be distributed. Since the platform expects messages to be + properly formatted SenML in order to be post-processed, clients are + obliged to specify Content-Type header for each published message. + Note that all messages that aren't SenML will be accepted and published, + but no post-processing will be applied. required: true content: application/json: diff --git a/http/README.md b/http/README.md index 8970f5bc..8ed6a3a0 100644 --- a/http/README.md +++ b/http/README.md @@ -8,16 +8,16 @@ The service is configured using the environment variables presented in the following table. Note that any unset variables will be replaced with their default values. -| Variable | Description | Default | -|--------------------------------|-----------------------------------------------------|-----------------------| -| MF_HTTP_ADAPTER_LOG_LEVEL | Log level for the HTTP Adapter | error | -| MF_HTTP_ADAPTER_PORT | Service HTTP port | 8180 | -| MF_NATS_URL | NATS instance URL | nats://localhost:4222 | -| MF_HTTP_ADAPTER_CLIENT_TLS | Flag that indicates if TLS should be turned on | false | -| MF_HTTP_ADAPTER_CA_CERTS | Path to trusted CAs in PEM format | | -| MF_JAEGER_URL | Jaeger server URL | localhost:6831 | -| MF_THINGS_AUTH_GRPC_URL | Things service Auth gRPC URL | localhost:8181 | -| MF_THINGS_AUTH_GRPC_TIMEOUT | Things service Auth gRPC request timeout in seconds | 1s | +| Variable | Description | Default | +| --------------------------- | --------------------------------------------------- | --------------------- | +| MF_HTTP_ADAPTER_LOG_LEVEL | Log level for the HTTP Adapter | error | +| MF_HTTP_ADAPTER_PORT | Service HTTP port | 8180 | +| MF_NATS_URL | NATS instance URL | nats://localhost:4222 | +| MF_HTTP_ADAPTER_CLIENT_TLS | Flag that indicates if TLS should be turned on | false | +| MF_HTTP_ADAPTER_CA_CERTS | Path to trusted CAs in PEM format | | +| MF_JAEGER_URL | Jaeger server URL | localhost:6831 | +| MF_THINGS_AUTH_GRPC_URL | Things service Auth gRPC URL | localhost:8181 | +| MF_THINGS_AUTH_GRPC_TIMEOUT | Things service Auth gRPC request timeout in seconds | 1s | ## Deployment @@ -53,7 +53,9 @@ Setting `MF_HTTP_ADAPTER_CA_CERTS` expects a file in PEM format of trusted CAs. ## Usage +HTTP Authorization request header contains the credentials to authenticate a Thing. The authorization header can be a plain Thing key +or a Thing key encoded as a password for Basic Authentication. In case the Basic Authentication schema is used, the username is ignored. For more information about service capabilities and its usage, please check out -the [API documentation](https://api.mainflux.io/?urls.primaryName=http-openapi.yml). +the [API documentation](https://api.mainflux.io/?urls.primaryName=http.yml). [doc]: https://docs.mainflux.io diff --git a/http/api/endpoint_test.go b/http/api/endpoint_test.go index 5e2e4b3b..54f2b009 100644 --- a/http/api/endpoint_test.go +++ b/http/api/endpoint_test.go @@ -37,6 +37,7 @@ type testRequest struct { contentType string token string body io.Reader + basicAuth bool } func (tr testRequest) make() (*http.Response, error) { @@ -44,9 +45,13 @@ func (tr testRequest) make() (*http.Response, error) { if err != nil { return nil, err } + if tr.token != "" { req.Header.Set("Authorization", tr.token) } + if tr.basicAuth && tr.token != "" { + req.SetBasicAuth("", tr.token) + } if tr.contentType != "" { req.Header.Set("Content-Type", tr.contentType) } @@ -70,6 +75,7 @@ func TestPublish(t *testing.T) { contentType string auth string status int + basicAuth bool }{ "publish message": { chanID: chanID, @@ -85,6 +91,14 @@ func TestPublish(t *testing.T) { auth: "", status: http.StatusForbidden, }, + "publish message with basic auth": { + chanID: chanID, + msg: msg, + contentType: contentType, + auth: token, + basicAuth: true, + status: http.StatusAccepted, + }, "publish message with invalid authorization token": { chanID: chanID, msg: msg, @@ -92,6 +106,14 @@ func TestPublish(t *testing.T) { auth: invalidToken, status: http.StatusForbidden, }, + "publish message with invalid basic auth": { + chanID: chanID, + msg: msg, + contentType: contentType, + auth: invalidToken, + basicAuth: true, + status: http.StatusForbidden, + }, "publish message without content type": { chanID: chanID, msg: msg, @@ -123,6 +145,7 @@ func TestPublish(t *testing.T) { contentType: tc.contentType, token: tc.auth, body: strings.NewReader(tc.msg), + basicAuth: tc.basicAuth, } res, err := req.make() assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", desc, err)) diff --git a/http/api/transport.go b/http/api/transport.go index 946ebcee..916da603 100644 --- a/http/api/transport.go +++ b/http/api/transport.go @@ -103,6 +103,10 @@ func decodeRequest(ctx context.Context, r *http.Request) (interface{}, error) { if err != nil { return nil, err } + token := r.Header.Get("Authorization") + if _, pass, ok := r.BasicAuth(); ok { + token = pass + } payload, err := decodePayload(r.Body) if err != nil { @@ -119,7 +123,7 @@ func decodeRequest(ctx context.Context, r *http.Request) (interface{}, error) { req := publishReq{ msg: msg, - token: r.Header.Get("Authorization"), + token: token, } return req, nil