blob: d04bafb5839e0362782b2b600dac652ef73adcc8 [file] [log] [blame]
// Copyright 2017 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 serviceaccounts
import (
"net"
"testing"
"time"
"golang.org/x/net/context"
"github.com/luci/gae/service/info"
"github.com/luci/luci-go/appengine/gaetesting"
"github.com/luci/luci-go/server/auth"
"github.com/luci/luci-go/server/auth/authdb"
"github.com/luci/luci-go/server/auth/authtest"
"github.com/luci/luci-go/server/auth/signing"
"github.com/luci/luci-go/server/auth/signing/signingtest"
"github.com/luci/luci-go/tokenserver/api/minter/v1"
"github.com/luci/luci-go/common/clock"
"github.com/luci/luci-go/common/clock/testclock"
"github.com/luci/luci-go/common/proto/google"
. "github.com/luci/luci-go/common/testing/assertions"
. "github.com/smartystreets/goconvey/convey"
)
func testingContext() context.Context {
ctx := gaetesting.TestingContext()
ctx = info.GetTestable(ctx).SetRequestID("gae-request-id")
ctx, _ = testclock.UseTime(ctx, time.Date(2015, time.February, 3, 4, 5, 6, 0, time.UTC))
return auth.WithState(ctx, &authtest.FakeState{
Identity: "user:requestor@example.com",
PeerIPOverride: net.ParseIP("127.10.10.10"),
FakeDB: &authdb.SnapshotDB{Rev: 1234},
})
}
func testingSigner() signing.Signer {
return signingtest.NewSigner(0, &signing.ServiceInfo{
ServiceAccountName: "signer@testing.host",
AppID: "unit-tests",
AppVersion: "mocked-ver",
})
}
func TestMintOAuthTokenGrant(t *testing.T) {
t.Parallel()
ctx := testingContext()
Convey("with mocked config and state", t, func() {
cfg, err := loadConfig(`rules {
name: "rule 1"
service_account: "account@robots.com"
proxy: "user:requestor@example.com"
end_user: "user:enduser@example.com"
max_grant_validity_duration: 7200
}`)
So(err, ShouldBeNil)
var lastParams *mintParams
mintMock := func(c context.Context, p *mintParams) (*minter.MintOAuthTokenGrantResponse, error) {
lastParams = p
expiry := clock.Now(c).Add(time.Duration(p.validityDuration) * time.Second)
return &minter.MintOAuthTokenGrantResponse{
GrantToken: "valid_token",
Expiry: google.NewTimestamp(expiry),
ServiceVersion: p.serviceVer,
}, nil
}
rpc := MintOAuthTokenGrantRPC{
Signer: testingSigner(),
Rules: func(context.Context) (*Rules, error) { return cfg, nil },
mintMock: mintMock,
}
Convey("Happy path", func() {
resp, err := rpc.MintOAuthTokenGrant(ctx, &minter.MintOAuthTokenGrantRequest{
ServiceAccount: "account@robots.com",
EndUser: "user:enduser@example.com",
})
So(err, ShouldBeNil)
So(resp.GrantToken, ShouldEqual, "valid_token")
So(resp.ServiceVersion, ShouldEqual, "unit-tests/mocked-ver")
So(lastParams, ShouldResemble, &mintParams{
serviceAccount: "account@robots.com",
proxyID: "user:requestor@example.com",
endUserID: "user:enduser@example.com",
validityDuration: 3600, // default
serviceVer: "unit-tests/mocked-ver",
})
})
Convey("Empty service account", func() {
_, err := rpc.MintOAuthTokenGrant(ctx, &minter.MintOAuthTokenGrantRequest{
EndUser: "user:enduser@example.com",
})
So(err, ShouldBeRPCInvalidArgument, "service_account is required")
})
Convey("Negative validity", func() {
_, err := rpc.MintOAuthTokenGrant(ctx, &minter.MintOAuthTokenGrantRequest{
ServiceAccount: "account@robots.com",
EndUser: "user:enduser@example.com",
ValidityDuration: -1,
})
So(err, ShouldBeRPCInvalidArgument, "validity_duration must be positive")
})
Convey("Empty end-user", func() {
_, err := rpc.MintOAuthTokenGrant(ctx, &minter.MintOAuthTokenGrantRequest{
ServiceAccount: "account@robots.com",
})
So(err, ShouldBeRPCInvalidArgument, "end_user is required")
})
Convey("Bad end-user", func() {
_, err := rpc.MintOAuthTokenGrant(ctx, &minter.MintOAuthTokenGrantRequest{
ServiceAccount: "account@robots.com",
EndUser: "blah",
})
So(err, ShouldBeRPCInvalidArgument, "bad identity string")
})
Convey("Unknown rule", func() {
_, err := rpc.MintOAuthTokenGrant(ctx, &minter.MintOAuthTokenGrantRequest{
ServiceAccount: "unknown@robots.com",
EndUser: "user:enduser@example.com",
})
So(err, ShouldBeRPCPermissionDenied, "unknown service account or not enough permissions to use it")
})
Convey("Unauthorized caller", func() {
ctx := auth.WithState(ctx, &authtest.FakeState{
Identity: "user:unknown@example.com",
})
_, err := rpc.MintOAuthTokenGrant(ctx, &minter.MintOAuthTokenGrantRequest{
ServiceAccount: "account@robots.com",
EndUser: "user:enduser@example.com",
})
So(err, ShouldBeRPCPermissionDenied, "unknown service account or not enough permissions to use it")
})
Convey("Too high validity duration", func() {
_, err := rpc.MintOAuthTokenGrant(ctx, &minter.MintOAuthTokenGrantRequest{
ServiceAccount: "account@robots.com",
EndUser: "user:enduser@example.com",
ValidityDuration: 7201,
})
So(err, ShouldBeRPCInvalidArgument, `per rule "rule 1" the validity duration should be <= 7200`)
})
Convey("Unauthorized end-user", func() {
_, err := rpc.MintOAuthTokenGrant(ctx, &minter.MintOAuthTokenGrantRequest{
ServiceAccount: "account@robots.com",
EndUser: "user:unknown@example.com",
})
So(err, ShouldBeRPCPermissionDenied,
`per rule "rule 1" the user "user:unknown@example.com" is not authorized to use the service account "account@robots.com"`)
})
})
}