Skip to content

Commit

Permalink
coap-gateway: check if oauth2 errors are temporary
Browse files Browse the repository at this point in the history
jwt package doesn't propagate correctly error:
golang/oauth2#635
  • Loading branch information
jkralik committed Mar 13, 2023
1 parent eb7fdbb commit 0d4ead4
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 3 deletions.
23 changes: 23 additions & 0 deletions coap-gateway/service/message/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import (
"bytes"
"context"
"errors"
"net/http"
"strings"

"github.com/plgd-dev/go-coap/v3/message"
"github.com/plgd-dev/go-coap/v3/message/codes"
"github.com/plgd-dev/go-coap/v3/message/pool"
"golang.org/x/oauth2"
grpcCodes "google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
Expand All @@ -27,6 +29,9 @@ func GetResponse(ctx context.Context, messagePool *pool.Pool, code codes.Code, t
}

func IsTempError(err error) bool {
if err == nil {
return false
}
var isTemporary interface {
Temporary() bool
}
Expand All @@ -48,6 +53,21 @@ func IsTempError(err error) bool {
return true
}
}

oauth2Err := &oauth2.RetrieveError{}
if errors.As(err, &oauth2Err) {
if oauth2Err.Response != nil {
switch oauth2Err.Response.StatusCode {
case http.StatusInternalServerError,
http.StatusServiceUnavailable,
http.StatusGatewayTimeout,
http.StatusRequestTimeout,
http.StatusTooManyRequests:
return true
}
}
}

switch {
// TODO: We could optimize this by using error.Is to avoid string comparison.
case strings.Contains(err.Error(), "connect: connection refused"),
Expand All @@ -57,6 +77,9 @@ func IsTempError(err error) bool {
strings.Contains(err.Error(), `write: broken pipe`),
strings.Contains(err.Error(), `request canceled while waiting for connection`),
strings.Contains(err.Error(), `authentication handshake failed`),
strings.Contains(strings.ToLower(err.Error()), `unavailable`),
strings.Contains(strings.ToLower(err.Error()), `temporarily`),
strings.Contains(strings.ToLower(err.Error()), `temporary`),
strings.Contains(err.Error(), context.DeadlineExceeded.Error()),
strings.Contains(err.Error(), context.Canceled.Error()):
return true
Expand Down
92 changes: 92 additions & 0 deletions coap-gateway/service/message/response_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package message

import (
"context"
"fmt"
"net/http"
"testing"

"github.com/stretchr/testify/assert"
"golang.org/x/oauth2"
"google.golang.org/grpc/status"
)

type testError struct {
isTemporary bool
isTimeout bool
grpcStatus status.Status
oauthErr oauth2.RetrieveError
}

func (e *testError) Error() string {
return "test error"
}

func (e *testError) Temporary() bool {
return e.isTemporary
}

func (e *testError) Timeout() bool {
return e.isTimeout
}

func (e *testError) GRPCStatus() *status.Status {
return &e.grpcStatus
}

func (e *testError) Unwrap() error {
return &e.oauthErr
}

func TestIsTempError(t *testing.T) {
type args struct {
err error
}
tests := []struct {
name string
args args
want bool
}{
{
name: "nil",
args: args{},
want: false,
},
{
name: "not temporary",
args: args{err: fmt.Errorf("err")},
want: false,
},
{
name: "temporary",
args: args{err: fmt.Errorf("err: %w", &testError{isTemporary: true})},
want: true,
},
{
name: "timeout",
args: args{err: fmt.Errorf("err: %w", &testError{isTimeout: true})},
want: true,
},
{
name: "grpcTemporary",
args: args{err: fmt.Errorf("err: %w", &testError{grpcStatus: *status.FromContextError(context.DeadlineExceeded)})},
want: true,
},
{
name: "oauth2Temporary",
args: args{err: fmt.Errorf("err: %w", &testError{oauthErr: oauth2.RetrieveError{Response: &http.Response{StatusCode: http.StatusServiceUnavailable}}})},
want: true,
},
{
name: "unavailable string",
args: args{err: fmt.Errorf("err: unavailable")},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := IsTempError(tt.args.err)
assert.Equal(t, tt.want, got)
})
}
}
4 changes: 3 additions & 1 deletion http-gateway/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ WORKDIR $GOPATH/src/github.com/plgd-dev/hub
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go mod vendor
RUN ( cd /usr/local/go && patch -p1 < $GOPATH/src/github.com/plgd-dev/hub/tools/docker/patches/shrink_tls_conn.patch )
RUN ( cd ./vendor/golang.org/x/oauth2 && patch -p1 < $GOPATH/src/github.com/plgd-dev/hub/tools/docker/patches/golang_org_x_oauth2_propagate_error.patch )
WORKDIR $GOPATH/src/github.com/plgd-dev/hub/http-gateway
RUN CGO_ENABLED=0 go build -o /go/bin/http-gateway ./cmd/service
RUN CGO_ENABLED=0 go build -mod=vendor -o /go/bin/http-gateway ./cmd/service

FROM node:16 AS build-web
COPY http-gateway/web /web
Expand Down
4 changes: 3 additions & 1 deletion tools/docker/Dockerfile.go1.18
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ WORKDIR $GOPATH/src/github.com/plgd-dev/hub
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go mod vendor
RUN ( cd /usr/local/go && patch -p1 < $GOPATH/src/github.com/plgd-dev/hub/tools/docker/patches/shrink_tls_conn.patch )
RUN ( cd ./vendor/golang.org/x/oauth2 && patch -p1 < $GOPATH/src/github.com/plgd-dev/hub/tools/docker/patches/golang_org_x_oauth2_propagate_error.patch )
WORKDIR $GOPATH/src/github.com/plgd-dev/hub/$DIRECTORY
RUN go build -o /go/bin/$NAME ./cmd/service
RUN go build -mod=vendor -o /go/bin/$NAME ./cmd/service

FROM alpine:3.17 AS service
ARG NAME
Expand Down
4 changes: 3 additions & 1 deletion tools/docker/Dockerfile.in
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ WORKDIR $GOPATH/src/github.com/plgd-dev/hub
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go mod vendor
RUN ( cd /usr/local/go && patch -p1 < $GOPATH/src/github.com/plgd-dev/hub/tools/docker/patches/shrink_tls_conn.patch )
RUN ( cd ./vendor/golang.org/x/oauth2 && patch -p1 < $GOPATH/src/github.com/plgd-dev/hub/tools/docker/patches/golang_org_x_oauth2_propagate_error.patch )
WORKDIR $GOPATH/src/github.com/plgd-dev/hub/@DIRECTORY@
RUN CGO_ENABLED=0 go build -o /go/bin/@NAME@ ./cmd/service
RUN CGO_ENABLED=0 go build -mod=vendor -o /go/bin/@NAME@ ./cmd/service

FROM alpine:3.17 AS security-provider
RUN apk add -U --no-cache ca-certificates
Expand Down
26 changes: 26 additions & 0 deletions tools/docker/patches/golang_org_x_oauth2_propagate_error.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
diff --git a/internal/oauth2.go b/internal/oauth2.go
index c0ab196..cad4b1f 100644
--- a/internal/oauth2.go
+++ b/internal/oauth2.go
@@ -26,7 +26,7 @@ func ParseKey(key []byte) (*rsa.PrivateKey, error) {
if err != nil {
parsedKey, err = x509.ParsePKCS1PrivateKey(key)
if err != nil {
- return nil, fmt.Errorf("private key should be a PEM or plain PKCS1 or PKCS8; parse error: %v", err)
+ return nil, fmt.Errorf("private key should be a PEM or plain PKCS1 or PKCS8; parse error: %w", err)
}
}
parsed, ok := parsedKey.(*rsa.PrivateKey)
diff --git a/internal/token.go b/internal/token.go
index b4723fc..7b96171 100644
--- a/internal/token.go
+++ b/internal/token.go
@@ -234,7 +234,7 @@ func doTokenRoundTrip(ctx context.Context, req *http.Request) (*Token, error) {
body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1<<20))
r.Body.Close()
if err != nil {
- return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
+ return nil, fmt.Errorf("oauth2: cannot fetch token: %w", err)
}
if code := r.StatusCode; code < 200 || code > 299 {
return nil, &RetrieveError{

0 comments on commit 0d4ead4

Please sign in to comment.