diff options
author | Jacob Vosmaer (GitLab) <jacob@gitlab.com> | 2018-04-11 16:14:53 +0300 |
---|---|---|
committer | Zeger-Jan van de Weg <zegerjan@gitlab.com> | 2018-04-11 16:14:53 +0300 |
commit | ca1f9fc86f7f301dc9dbb8245180de8460d87c06 (patch) | |
tree | 96e66c7b77add9ae0eb0955d4a36dc545d4f43e4 /auth | |
parent | 4670e7da8549506752627357bb05ad62332274c9 (diff) |
Make more of the auth code public so we can reuse it
Diffstat (limited to 'auth')
-rw-r--r-- | auth/extract_test.go | 68 | ||||
-rw-r--r-- | auth/token.go | 45 |
2 files changed, 113 insertions, 0 deletions
diff --git a/auth/extract_test.go b/auth/extract_test.go new file mode 100644 index 000000000..f2cc3f773 --- /dev/null +++ b/auth/extract_test.go @@ -0,0 +1,68 @@ +package gitalyauth + +import ( + "testing" + + "github.com/stretchr/testify/require" + "golang.org/x/net/context" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +func TestCheckToken(t *testing.T) { + secret := "secret 1234" + + testCases := []struct { + desc string + md metadata.MD + code codes.Code + }{ + { + desc: "ok", + md: credsMD(t, RPCCredentials(secret)), + code: codes.OK, + }, + { + desc: "denied", + md: credsMD(t, RPCCredentials("wrong secret")), + code: codes.PermissionDenied, + }, + { + desc: "invalid, not bearer", + md: credsMD(t, &invalidCreds{"foobar"}), + code: codes.Unauthenticated, + }, + { + desc: "invalid, bearer but not base64", + md: credsMD(t, &invalidCreds{"Bearer foo!!bar"}), + code: codes.Unauthenticated, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + ctx := metadata.NewIncomingContext(context.Background(), tc.md) + err := CheckToken(ctx, secret) + require.Equal(t, tc.code, status.Code(err), "expected grpc code in error %v", err) + }) + } + +} + +func credsMD(t *testing.T, creds credentials.PerRPCCredentials) metadata.MD { + md, err := creds.GetRequestMetadata(context.Background()) + require.NoError(t, err) + return metadata.New(md) +} + +type invalidCreds struct { + authHeader string +} + +func (invalidCreds) RequireTransportSecurity() bool { return false } + +func (ic *invalidCreds) GetRequestMetadata(context.Context, ...string) (map[string]string, error) { + return map[string]string{"authorization": ic.authHeader}, nil +} diff --git a/auth/token.go b/auth/token.go new file mode 100644 index 000000000..fc13c5bee --- /dev/null +++ b/auth/token.go @@ -0,0 +1,45 @@ +package gitalyauth + +import ( + "crypto/subtle" + "encoding/base64" + + "github.com/grpc-ecosystem/go-grpc-middleware/auth" + "golang.org/x/net/context" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +var ( + errUnauthenticated = status.Errorf(codes.Unauthenticated, "authentication required") + errDenied = status.Errorf(codes.PermissionDenied, "permission denied") +) + +// CheckToken checks the 'authentication' header of incoming gRPC +// metadata in ctx. It returns nil if and only if the token matches +// secret. +func CheckToken(ctx context.Context, secret string) error { + if len(secret) == 0 { + panic("CheckToken: secret may not be empty") + } + + encodedToken, err := grpc_auth.AuthFromMD(ctx, "bearer") + if err != nil { + return errUnauthenticated + } + + token, err := base64.StdEncoding.DecodeString(encodedToken) + if err != nil { + return errUnauthenticated + } + + if !tokensEqual(token, []byte(secret)) { + return errDenied + } + + return nil +} + +func tokensEqual(tok1, tok2 []byte) bool { + return subtle.ConstantTimeCompare(tok1, tok2) == 1 +} |