blob: 911efc1ae8c589909f89c3799e1f2dcc516bfa75 [file] [log] [blame]
// Copyright 2021 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gerritauth
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"testing"
"time"
"go.chromium.org/luci/common/clock/testclock"
"go.chromium.org/luci/server/auth"
"go.chromium.org/luci/server/auth/signing/signingtest"
. "github.com/smartystreets/goconvey/convey"
. "go.chromium.org/luci/common/testing/assertions"
)
func TestAuthMethod(t *testing.T) {
t.Parallel()
Convey("With mocks", t, func() {
const expectedHeader = "X-Gerrit-Auth"
const expectedAudience = "good-audience"
assertedUser := AssertedUser{
AccountID: 12345,
Emails: []string{"xyz@example.com", "abc@example.com"},
PreferredEmail: "abc@example.com",
}
assertedChange := AssertedChange{
Host: "some-host",
Repository: "some/repo",
ChangeNumber: 99999,
}
now := time.Unix(1500000000, 0).UTC()
ctx, _ := testclock.UseTime(context.Background(), now)
signer := signingtest.NewSigner(nil)
certs, _ := signer.Certificates(ctx)
goodKeyID := signer.KeyNameForTest()
method := AuthMethod{
Header: expectedHeader,
SignerAccount: "unused-in-the-test",
Audience: expectedAudience,
testCerts: certs,
}
prepareJWT := func(tok gerritJWT) string {
bodyBlob, err := json.Marshal(&tok)
So(err, ShouldBeNil)
b64hdr := base64.RawURLEncoding.EncodeToString([]byte(
fmt.Sprintf(`{"alg": "RS256","kid": "%s"}`, goodKeyID),
))
b64bdy := base64.RawURLEncoding.EncodeToString(bodyBlob)
_, sig, err := signer.SignBytes(ctx, []byte(b64hdr+"."+b64bdy))
So(err, ShouldBeNil)
return b64hdr + "." + b64bdy + "." + base64.RawURLEncoding.EncodeToString(sig)
}
call := func(tok string) (*auth.User, error) {
req, err := http.NewRequest("GET", "https://unimportant.example.com", nil)
req.Header.Add(expectedHeader, tok)
So(err, ShouldBeNil)
user, _, err := method.Authenticate(ctx, req)
return user, err
}
Convey("Success", func() {
user, err := call(prepareJWT(gerritJWT{
Aud: expectedAudience,
Exp: now.Add(5 * time.Minute).Unix(),
AssertedUser: assertedUser,
AssertedChange: assertedChange,
}))
So(err, ShouldBeNil)
So(user, ShouldResemble, &auth.User{
Identity: "user:abc@example.com",
Email: "abc@example.com",
Extra: &AssertedInfo{
User: assertedUser,
Change: assertedChange,
},
})
})
Convey("Success, but no preferred email", func() {
user, err := call(prepareJWT(gerritJWT{
Aud: expectedAudience,
Exp: now.Add(5 * time.Minute).Unix(),
AssertedUser: AssertedUser{
Emails: []string{"xyz@example.com", "abc@example.com"},
},
AssertedChange: assertedChange,
}))
So(err, ShouldBeNil)
So(user, ShouldResemble, &auth.User{
Identity: "user:xyz@example.com",
Email: "xyz@example.com",
Extra: &AssertedInfo{
User: AssertedUser{
Emails: []string{"xyz@example.com", "abc@example.com"},
},
Change: assertedChange,
},
})
})
Convey("Unconfigured", func() {
method.SignerAccount = ""
user, err := call("ignored")
So(err, ShouldBeNil)
So(user, ShouldBeNil)
})
Convey("Missing header", func() {
method.Header = "Something-Else"
user, err := call("ignored")
So(err, ShouldBeNil)
So(user, ShouldBeNil)
})
Convey("Bad token", func() {
_, err := call("blah.blah.blah")
So(err, ShouldErrLike, "bad Gerrit JWT")
})
Convey("Bad audience", func() {
_, err := call(prepareJWT(gerritJWT{
Aud: "wrong-audience",
Exp: now.Add(5 * time.Minute).Unix(),
AssertedUser: assertedUser,
AssertedChange: assertedChange,
}))
So(err, ShouldErrLike, "bad Gerrit JWT: wrong audience")
})
Convey("Expired token", func() {
_, err := call(prepareJWT(gerritJWT{
Aud: expectedAudience,
Exp: now.Add(-5 * time.Minute).Unix(),
AssertedUser: assertedUser,
AssertedChange: assertedChange,
}))
So(err, ShouldErrLike, "bad Gerrit JWT: expired")
})
Convey("No emails", func() {
_, err := call(prepareJWT(gerritJWT{
Aud: expectedAudience,
Exp: now.Add(5 * time.Minute).Unix(),
AssertedChange: assertedChange,
}))
So(err, ShouldErrLike, "bad Gerrit JWT: asserted_user.preferred_email and asserted_user.emails are empty")
})
Convey("Invalid email", func() {
_, err := call(prepareJWT(gerritJWT{
Aud: expectedAudience,
Exp: now.Add(5 * time.Minute).Unix(),
AssertedUser: AssertedUser{
PreferredEmail: "this-is-not-an-email",
Emails: []string{
"unused@example.com",
},
},
AssertedChange: assertedChange,
}))
So(err, ShouldErrLike, "bad Gerrit JWT: unrecognized email format")
})
})
}