go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/gerritauth/method_test.go (about) 1 // Copyright 2021 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package gerritauth 16 17 import ( 18 "context" 19 "encoding/base64" 20 "encoding/json" 21 "fmt" 22 "testing" 23 "time" 24 25 "go.chromium.org/luci/common/clock/testclock" 26 27 "go.chromium.org/luci/server/auth" 28 "go.chromium.org/luci/server/auth/authtest" 29 "go.chromium.org/luci/server/auth/signing/signingtest" 30 31 . "github.com/smartystreets/goconvey/convey" 32 . "go.chromium.org/luci/common/testing/assertions" 33 ) 34 35 func TestAuthMethod(t *testing.T) { 36 t.Parallel() 37 38 Convey("With mocks", t, func() { 39 const expectedHeader = "X-Gerrit-Auth" 40 const expectedAudience = "good-audience" 41 42 assertedUser := AssertedUser{ 43 AccountID: 12345, 44 Emails: []string{"xyz@example.com", "abc@example.com"}, 45 PreferredEmail: "abc@example.com", 46 } 47 assertedChange := AssertedChange{ 48 Host: "some-host", 49 Repository: "some/repo", 50 ChangeNumber: 99999, 51 } 52 53 now := time.Unix(1500000000, 0).UTC() 54 ctx, _ := testclock.UseTime(context.Background(), now) 55 56 signer := signingtest.NewSigner(nil) 57 certs, _ := signer.Certificates(ctx) 58 goodKeyID := signer.KeyNameForTest() 59 60 method := AuthMethod{ 61 Header: expectedHeader, 62 SignerAccounts: []string{"trusted-issuer"}, 63 Audience: expectedAudience, 64 testCerts: certs, 65 } 66 67 prepareJWT := func(tok gerritJWT) string { 68 bodyBlob, err := json.Marshal(&tok) 69 So(err, ShouldBeNil) 70 b64hdr := base64.RawURLEncoding.EncodeToString([]byte( 71 fmt.Sprintf(`{"alg": "RS256","kid": "%s"}`, goodKeyID), 72 )) 73 b64bdy := base64.RawURLEncoding.EncodeToString(bodyBlob) 74 _, sig, err := signer.SignBytes(ctx, []byte(b64hdr+"."+b64bdy)) 75 So(err, ShouldBeNil) 76 return b64hdr + "." + b64bdy + "." + base64.RawURLEncoding.EncodeToString(sig) 77 } 78 79 call := func(tok string) (*auth.User, error) { 80 req := authtest.NewFakeRequestMetadata() 81 req.FakeHeader.Add(expectedHeader, tok) 82 user, _, err := method.Authenticate(ctx, req) 83 return user, err 84 } 85 86 Convey("Success", func() { 87 user, err := call(prepareJWT(gerritJWT{ 88 Iss: "trusted-issuer", 89 Aud: expectedAudience, 90 Exp: now.Add(5 * time.Minute).Unix(), 91 AssertedUser: assertedUser, 92 AssertedChange: assertedChange, 93 })) 94 So(err, ShouldBeNil) 95 So(user, ShouldResemble, &auth.User{ 96 Identity: "user:abc@example.com", 97 Email: "abc@example.com", 98 Extra: &AssertedInfo{ 99 User: assertedUser, 100 Change: assertedChange, 101 }, 102 }) 103 }) 104 105 Convey("Success, but no preferred email", func() { 106 user, err := call(prepareJWT(gerritJWT{ 107 Iss: "trusted-issuer", 108 Aud: expectedAudience, 109 Exp: now.Add(5 * time.Minute).Unix(), 110 AssertedUser: AssertedUser{ 111 Emails: []string{"xyz@example.com", "abc@example.com"}, 112 }, 113 AssertedChange: assertedChange, 114 })) 115 So(err, ShouldBeNil) 116 So(user, ShouldResemble, &auth.User{ 117 Identity: "user:xyz@example.com", 118 Email: "xyz@example.com", 119 Extra: &AssertedInfo{ 120 User: AssertedUser{ 121 Emails: []string{"xyz@example.com", "abc@example.com"}, 122 }, 123 Change: assertedChange, 124 }, 125 }) 126 }) 127 128 Convey("Unconfigured", func() { 129 method.SignerAccounts = nil 130 user, err := call("ignored") 131 So(err, ShouldBeNil) 132 So(user, ShouldBeNil) 133 }) 134 135 Convey("Missing header", func() { 136 method.Header = "Something-Else" 137 user, err := call("ignored") 138 So(err, ShouldBeNil) 139 So(user, ShouldBeNil) 140 }) 141 142 Convey("Bad token", func() { 143 _, err := call("blah.blah.blah") 144 So(err, ShouldErrLike, "bad Gerrit JWT") 145 }) 146 147 Convey("Unrecognized issuer", func() { 148 _, err := call(prepareJWT(gerritJWT{ 149 Iss: "unknown-issuer", 150 Aud: expectedAudience, 151 Exp: now.Add(5 * time.Minute).Unix(), 152 AssertedUser: assertedUser, 153 AssertedChange: assertedChange, 154 })) 155 So(err, ShouldErrLike, "bad Gerrit JWT") 156 }) 157 158 Convey("Bad audience", func() { 159 _, err := call(prepareJWT(gerritJWT{ 160 Iss: "trusted-issuer", 161 Aud: "wrong-audience", 162 Exp: now.Add(5 * time.Minute).Unix(), 163 AssertedUser: assertedUser, 164 AssertedChange: assertedChange, 165 })) 166 So(err, ShouldErrLike, "bad Gerrit JWT: wrong audience") 167 }) 168 169 Convey("Expired token", func() { 170 _, err := call(prepareJWT(gerritJWT{ 171 Iss: "trusted-issuer", 172 Aud: expectedAudience, 173 Exp: now.Add(-5 * time.Minute).Unix(), 174 AssertedUser: assertedUser, 175 AssertedChange: assertedChange, 176 })) 177 So(err, ShouldErrLike, "bad Gerrit JWT: expired") 178 }) 179 180 Convey("No emails", func() { 181 _, err := call(prepareJWT(gerritJWT{ 182 Iss: "trusted-issuer", 183 Aud: expectedAudience, 184 Exp: now.Add(5 * time.Minute).Unix(), 185 AssertedChange: assertedChange, 186 })) 187 So(err, ShouldErrLike, "bad Gerrit JWT: asserted_user.preferred_email and asserted_user.emails are empty") 188 }) 189 190 Convey("Invalid email", func() { 191 _, err := call(prepareJWT(gerritJWT{ 192 Iss: "trusted-issuer", 193 Aud: expectedAudience, 194 Exp: now.Add(5 * time.Minute).Unix(), 195 AssertedUser: AssertedUser{ 196 PreferredEmail: "this-is-not-an-email", 197 Emails: []string{ 198 "unused@example.com", 199 }, 200 }, 201 AssertedChange: assertedChange, 202 })) 203 So(err, ShouldErrLike, "bad Gerrit JWT: unrecognized email format") 204 }) 205 }) 206 }