go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/auth/service/service_test.go (about) 1 // Copyright 2015 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 service 16 17 import ( 18 "bytes" 19 "compress/zlib" 20 "context" 21 "crypto/sha256" 22 "encoding/base64" 23 "encoding/hex" 24 "encoding/json" 25 "fmt" 26 "net/http" 27 "net/http/httptest" 28 "testing" 29 "time" 30 31 "google.golang.org/protobuf/proto" 32 33 "go.chromium.org/luci/server/auth/service/protocol" 34 "go.chromium.org/luci/server/auth/signing" 35 "go.chromium.org/luci/server/auth/signing/signingtest" 36 "go.chromium.org/luci/server/caching" 37 38 . "github.com/smartystreets/goconvey/convey" 39 . "go.chromium.org/luci/common/testing/assertions" 40 ) 41 42 func TestPubSubWorkflow(t *testing.T) { 43 Convey("PubSub pull workflow works", t, func(c C) { 44 ctx := caching.WithEmptyProcessCache(context.Background()) 45 46 fakeSigner := signingtest.NewSigner(nil) 47 certs, _ := fakeSigner.Certificates(ctx) 48 certsJSON, _ := json.Marshal(certs) 49 50 // Expected calls. 51 calls := []struct { 52 Method string 53 URL string 54 Code int 55 Response string 56 }{ 57 // Probing for existing subscription -> tell it is not found. 58 { 59 "GET", 60 "/pubsub/projects/p1/subscriptions/sub", 61 404, 62 `{"error": {"code": 404}}`, 63 }, 64 // Authorizing access to PubSub topic. 65 { 66 "POST", 67 "/auth_service/api/v1/authdb/subscription/authorization", 68 200, 69 `{"topic": "projects/p2/topics/topic"}`, 70 }, 71 // Creating the subscription. 72 { 73 "PUT", 74 "/pubsub/projects/p1/subscriptions/sub", 75 200, 76 "", 77 }, 78 // Probing for existing subscription again. 79 { 80 "GET", 81 "/pubsub/projects/p1/subscriptions/sub", 82 200, 83 `{"pushConfig": {"pushEndpoint": "http://blah"}}`, 84 }, 85 // Changing push URL. 86 { 87 "POST", 88 "/pubsub/projects/p1/subscriptions/sub:modifyPushConfig", 89 200, 90 "", 91 }, 92 // Pulling messages from it, all bad. 93 { 94 "POST", 95 "/pubsub/projects/p1/subscriptions/sub:pull", 96 200, 97 `{"receivedMessages": [ 98 { 99 "ackId": "ack1", 100 "message": {"data": "broken"} 101 } 102 ]}`, 103 }, 104 // Fetching certificates from auth service to authenticate messages. 105 { 106 "GET", 107 "/auth/api/v1/server/certificates", 108 200, 109 string(certsJSON), 110 }, 111 // Bad messages are removed from the queue by ack. 112 { 113 "POST", 114 "/pubsub/projects/p1/subscriptions/sub:acknowledge", 115 200, 116 "", 117 }, 118 // Pulling messages from the subscription, again. 119 { 120 "POST", 121 "/pubsub/projects/p1/subscriptions/sub:pull", 122 200, 123 fmt.Sprintf(`{"receivedMessages": [ 124 { 125 "ackId": "ack1", 126 "message": {"data": "broken"} 127 }, 128 %s, 129 %s 130 ]}`, fakePubSubMessage(ctx, "ack2", 122, fakeSigner), fakePubSubMessage(ctx, "ack2", 123, fakeSigner)), 131 }, 132 // Acknowledging messages. 133 { 134 "POST", 135 "/pubsub/projects/p1/subscriptions/sub:acknowledge", 136 200, 137 "", 138 }, 139 // Removing existing subscription. 140 { 141 "DELETE", 142 "/pubsub/projects/p1/subscriptions/sub", 143 200, 144 "{}", 145 }, 146 // Removing already deleted subscription. 147 { 148 "DELETE", 149 "/pubsub/projects/p1/subscriptions/sub", 150 404, 151 `{"error": {"code": 404}}`, 152 }, 153 } 154 counter := 0 155 156 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 157 if counter >= len(calls) { 158 c.So(fmt.Sprintf("%s %s is unexpected", r.Method, r.URL.Path), ShouldBeNil) 159 } 160 call := &calls[counter] 161 c.So(r.Method, ShouldEqual, call.Method) 162 c.So(r.URL.Path, ShouldEqual, call.URL) 163 w.WriteHeader(call.Code) 164 if call.Response != "" { 165 w.Write([]byte(call.Response)) 166 } 167 counter++ 168 })) 169 defer ts.Close() 170 171 // Register. 172 srv := AuthService{ 173 URL: ts.URL, 174 pubSubURLRoot: ts.URL + "/pubsub/", 175 } 176 So(srv.EnsureSubscription(ctx, "projects/p1/subscriptions/sub", "http://blah"), ShouldBeNil) 177 178 // Reregister with no push url. For code coverage. 179 So(srv.EnsureSubscription(ctx, "projects/p1/subscriptions/sub", ""), ShouldBeNil) 180 181 // First pull. No valid messages. 182 notify, err := srv.PullPubSub(ctx, "projects/p1/subscriptions/sub") 183 So(err, ShouldBeNil) 184 So(notify, ShouldBeNil) 185 186 // Second pull. Have something. 187 notify, err = srv.PullPubSub(ctx, "projects/p1/subscriptions/sub") 188 So(err, ShouldBeNil) 189 So(notify, ShouldNotBeNil) 190 So(notify.Revision, ShouldEqual, 123) 191 192 // Ack. 193 So(notify.Acknowledge(ctx), ShouldBeNil) 194 195 // Code coverage. 196 notify, err = srv.ProcessPubSubPush(ctx, []byte(fakePubSubMessage(ctx, "", 456, fakeSigner))) 197 So(err, ShouldBeNil) 198 So(notify, ShouldNotBeNil) 199 So(notify.Revision, ShouldEqual, 456) 200 201 // Killing existing subscription. 202 So(srv.DeleteSubscription(ctx, "projects/p1/subscriptions/sub"), ShouldBeNil) 203 // Killing already removed subscription. 204 So(srv.DeleteSubscription(ctx, "projects/p1/subscriptions/sub"), ShouldBeNil) 205 }) 206 } 207 208 func TestGetSnapshot(t *testing.T) { 209 Convey("GetSnapshot works", t, func(c C) { 210 ctx := context.Background() 211 212 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 213 c.So(r.URL.Path, ShouldEqual, "/auth_service/api/v1/authdb/revisions/123") 214 body, digest := generateSnapshot(123) 215 w.Write([]byte(fmt.Sprintf(`{"snapshot": { 216 "auth_db_rev": 123, 217 "sha256": "%s", 218 "created_ts": 1446599918304238, 219 "deflated_body": "%s" 220 }}`, digest, body))) 221 })) 222 defer ts.Close() 223 224 srv := AuthService{ 225 URL: ts.URL, 226 } 227 snap, err := srv.GetSnapshot(ctx, 123) 228 So(err, ShouldBeNil) 229 230 So(snap, ShouldResemble, &Snapshot{ 231 AuthDB: &protocol.AuthDB{}, 232 AuthServiceURL: ts.URL, 233 Rev: 123, 234 Created: time.Unix(0, 1446599918304238000), 235 }) 236 }) 237 } 238 239 func TestDeflateInflate(t *testing.T) { 240 Convey("Deflate then Inflate works", t, func() { 241 initial := &protocol.AuthDB{ 242 OauthClientId: "abc", 243 OauthClientSecret: "def", 244 } 245 blob, err := DeflateAuthDB(initial) 246 So(err, ShouldBeNil) 247 inflated, err := InflateAuthDB(blob) 248 So(err, ShouldBeNil) 249 So(inflated, ShouldResembleProto, initial) 250 }) 251 } 252 253 /// 254 255 func generateSnapshot(rev int64) (body string, digest string) { 256 blob, err := proto.Marshal(&protocol.ReplicationPushRequest{ 257 Revision: &protocol.AuthDBRevision{ 258 AuthDbRev: rev, 259 PrimaryId: "primaryId", 260 ModifiedTs: 1446599918304238, 261 }, 262 AuthDb: &protocol.AuthDB{}, 263 }) 264 if err != nil { 265 panic(err) 266 } 267 268 buf := bytes.Buffer{} 269 w := zlib.NewWriter(&buf) 270 _, err = w.Write(blob) 271 if err != nil { 272 panic(err) 273 } 274 w.Close() 275 276 body = base64.StdEncoding.EncodeToString(buf.Bytes()) 277 hash := sha256.Sum256(blob) 278 digest = hex.EncodeToString(hash[:]) 279 return 280 } 281 282 func fakePubSubMessage(ctx context.Context, ackID string, rev int64, signer signing.Signer) string { 283 msg := protocol.ChangeNotification{ 284 Revision: &protocol.AuthDBRevision{ 285 AuthDbRev: rev, 286 PrimaryId: "primaryId", 287 ModifiedTs: 1000, 288 }, 289 } 290 blob, _ := proto.Marshal(&msg) 291 key, sig, _ := signer.SignBytes(ctx, blob) 292 ps := pubSubMessage{ 293 AckID: ackID, 294 } 295 ps.Message.Data = base64.StdEncoding.EncodeToString(blob) 296 ps.Message.Attributes = map[string]string{ 297 "X-AuthDB-SigKey-v1": key, 298 "X-AuthDB-SigVal-v1": base64.StdEncoding.EncodeToString(sig), 299 } 300 out, _ := json.Marshal(&ps) 301 return string(out) 302 }