blob: dae2806cf06692d8a1693b4c4dc935ed9f2a7760 [file] [log] [blame]
// Copyright 2016 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 iam
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"time"
"go.chromium.org/luci/common/clock/testclock"
. "github.com/smartystreets/goconvey/convey"
)
func TestCredentialsClient(t *testing.T) {
t.Parallel()
Convey("SignBlob works", t, func(c C) {
bodies := make(chan []byte, 1)
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
if r.URL.Path != "/v1/projects/-/serviceAccounts/abc@example.com:signBlob" {
w.WriteHeader(http.StatusNotFound)
return
}
body, err := ioutil.ReadAll(r.Body)
if err != nil {
panic(err)
}
bodies <- body
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
w.Write([]byte(
fmt.Sprintf(`{"keyId":"key_id","signedBlob":"%s"}`,
base64.StdEncoding.EncodeToString([]byte("signature")))))
}))
defer ts.Close()
cl := CredentialsClient{
Client: http.DefaultClient,
backendURL: ts.URL,
}
keyID, sig, err := cl.SignBlob(context.Background(), "abc@example.com", []byte("blob"))
So(err, ShouldBeNil)
So(keyID, ShouldEqual, "key_id")
So(string(sig), ShouldEqual, "signature")
// The request body looks sane too.
body := <-bodies
So(string(body), ShouldEqual, `{"payload":"YmxvYg=="}`)
})
Convey("SignJWT works", t, func(c C) {
bodies := make(chan []byte, 1)
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
if r.URL.Path != "/v1/projects/-/serviceAccounts/abc@example.com:signJwt" {
w.WriteHeader(http.StatusNotFound)
return
}
body, err := ioutil.ReadAll(r.Body)
if err != nil {
panic(err)
}
bodies <- body
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
w.Write([]byte(`{"keyId":"key_id","signedJwt":"signed_jwt"}`))
}))
defer ts.Close()
cl := CredentialsClient{
Client: http.DefaultClient,
backendURL: ts.URL,
}
keyID, jwt, err := cl.SignJWT(context.Background(), "abc@example.com", &ClaimSet{Exp: 123})
So(err, ShouldBeNil)
So(keyID, ShouldEqual, "key_id")
So(jwt, ShouldEqual, "signed_jwt")
// The request body looks sane too.
body := <-bodies
So(string(body), ShouldEqual, `{"payload":"{\"iss\":\"\",\"aud\":\"\",\"exp\":123,\"iat\":0}"}`)
})
Convey("GenerateAccessToken works", t, func(c C) {
expireTime := testclock.TestRecentTimeUTC.Round(time.Second)
var body map[string]interface{}
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
switch r.URL.Path {
case "/v1/projects/-/serviceAccounts/abc@example.com:generateAccessToken":
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
c.Printf("Bad body: %s\n", err)
w.WriteHeader(500)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
resp := fmt.Sprintf(`{"accessToken":"%s","expireTime":"%s"}`, "token1", expireTime.Format(time.RFC3339))
w.Write([]byte(resp))
default:
c.Printf("Unknown URL: %q\n", r.URL.Path)
w.WriteHeader(404)
}
}))
defer ts.Close()
cl := CredentialsClient{
Client: http.DefaultClient,
backendURL: ts.URL,
}
token, err := cl.GenerateAccessToken(context.Background(),
"abc@example.com", []string{"a", "b"}, []string{"deleg"}, 30*time.Minute)
So(err, ShouldBeNil)
So(token.AccessToken, ShouldEqual, "token1")
So(token.Expiry, ShouldResemble, expireTime)
So(body, ShouldResemble, map[string]interface{}{
"delegates": []interface{}{"deleg"},
"scope": []interface{}{"a", "b"},
"lifetime": "30m0s",
})
})
Convey("GenerateIDToken works", t, func(c C) {
var body map[string]interface{}
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
switch r.URL.Path {
case "/v1/projects/-/serviceAccounts/abc@example.com:generateIdToken":
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
c.Printf("Bad body: %s\n", err)
w.WriteHeader(500)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
w.Write([]byte(`{"token":"fake_id_token"}`))
default:
c.Printf("Unknown URL: %q\n", r.URL.Path)
w.WriteHeader(404)
}
}))
defer ts.Close()
cl := CredentialsClient{
Client: http.DefaultClient,
backendURL: ts.URL,
}
token, err := cl.GenerateIDToken(context.Background(),
"abc@example.com", "aud", true, []string{"deleg"})
So(err, ShouldBeNil)
So(token, ShouldEqual, "fake_id_token")
So(body, ShouldResemble, map[string]interface{}{
"delegates": []interface{}{"deleg"},
"audience": "aud",
"includeEmail": true,
})
})
}