| OLD | NEW |
| 1 // Copyright 2016 The LUCI Authors. All rights reserved. | 1 // Copyright 2016 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. | 3 // that can be found in the LICENSE file. |
| 4 | 4 |
| 5 package auth | 5 package auth |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "bytes" | 8 "bytes" |
| 9 "io/ioutil" | 9 "io/ioutil" |
| 10 "net/http" | 10 "net/http" |
| 11 "strings" |
| 11 "testing" | 12 "testing" |
| 12 "time" | 13 "time" |
| 13 | 14 |
| 14 "golang.org/x/net/context" | 15 "golang.org/x/net/context" |
| 16 "golang.org/x/oauth2" |
| 15 | 17 |
| 16 "github.com/luci/luci-go/common/auth" | 18 "github.com/luci/luci-go/common/auth" |
| 17 "github.com/luci/luci-go/server/auth/delegation" | 19 "github.com/luci/luci-go/server/auth/delegation" |
| 18 "github.com/luci/luci-go/server/auth/identity" | 20 "github.com/luci/luci-go/server/auth/identity" |
| 19 . "github.com/smartystreets/goconvey/convey" | 21 . "github.com/smartystreets/goconvey/convey" |
| 20 ) | 22 ) |
| 21 | 23 |
| 22 func TestGetRPCTransport(t *testing.T) { | 24 func TestGetRPCTransport(t *testing.T) { |
| 25 t.Parallel() |
| 26 |
| 23 Convey("GetRPCTransport works", t, func() { | 27 Convey("GetRPCTransport works", t, func() { |
| 24 ctx := context.Background() | 28 ctx := context.Background() |
| 25 mock := &clientRPCTransportMock{} | 29 mock := &clientRPCTransportMock{} |
| 26 ctx = ModifyConfig(ctx, func(cfg *Config) { | 30 ctx = ModifyConfig(ctx, func(cfg *Config) { |
| 27 cfg.AccessTokenProvider = mock.getAccessToken | 31 cfg.AccessTokenProvider = mock.getAccessToken |
| 28 cfg.AnonymousTransport = mock.getTransport | 32 cfg.AnonymousTransport = mock.getTransport |
| 29 }) | 33 }) |
| 30 | 34 |
| 31 Convey("in NoAuth mode", func(c C) { | 35 Convey("in NoAuth mode", func(c C) { |
| 32 t, err := GetRPCTransport(ctx, NoAuth) | 36 t, err := GetRPCTransport(ctx, NoAuth) |
| 33 So(err, ShouldBeNil) | 37 So(err, ShouldBeNil) |
| 34 _, err = t.RoundTrip(makeReq("https://example.com")) | 38 _, err = t.RoundTrip(makeReq("https://example.com")) |
| 35 So(err, ShouldBeNil) | 39 So(err, ShouldBeNil) |
| 36 | 40 |
| 37 So(len(mock.calls), ShouldEqual, 0) | 41 So(len(mock.calls), ShouldEqual, 0) |
| 38 So(len(mock.reqs[0].Header), ShouldEqual, 0) | 42 So(len(mock.reqs[0].Header), ShouldEqual, 0) |
| 39 }) | 43 }) |
| 40 | 44 |
| 41 Convey("in AsSelf mode", func(c C) { | 45 Convey("in AsSelf mode", func(c C) { |
| 42 t, err := GetRPCTransport(ctx, AsSelf, WithScopes("A", "
B")) | 46 t, err := GetRPCTransport(ctx, AsSelf, WithScopes("A", "
B")) |
| 43 So(err, ShouldBeNil) | 47 So(err, ShouldBeNil) |
| 44 _, err = t.RoundTrip(makeReq("https://example.com")) | 48 _, err = t.RoundTrip(makeReq("https://example.com")) |
| 45 So(err, ShouldBeNil) | 49 So(err, ShouldBeNil) |
| 46 | 50 |
| 47 So(mock.calls[0], ShouldResemble, []string{"A", "B"}) | 51 So(mock.calls[0], ShouldResemble, []string{"A", "B"}) |
| 48 So(mock.reqs[0].Header, ShouldResemble, http.Header{ | 52 So(mock.reqs[0].Header, ShouldResemble, http.Header{ |
| 49 » » » » "Authorization": {"Bearer blah"}, | 53 » » » » "Authorization": {"Bearer blah:A,B"}, |
| 50 }) | 54 }) |
| 51 }) | 55 }) |
| 52 | 56 |
| 53 Convey("in AsSelf mode with default scopes", func(c C) { | 57 Convey("in AsSelf mode with default scopes", func(c C) { |
| 54 t, err := GetRPCTransport(ctx, AsSelf) | 58 t, err := GetRPCTransport(ctx, AsSelf) |
| 55 So(err, ShouldBeNil) | 59 So(err, ShouldBeNil) |
| 56 _, err = t.RoundTrip(makeReq("https://example.com")) | 60 _, err = t.RoundTrip(makeReq("https://example.com")) |
| 57 So(err, ShouldBeNil) | 61 So(err, ShouldBeNil) |
| 58 | 62 |
| 59 So(mock.calls[0], ShouldResemble, []string{"https://www.
googleapis.com/auth/userinfo.email"}) | 63 So(mock.calls[0], ShouldResemble, []string{"https://www.
googleapis.com/auth/userinfo.email"}) |
| 60 So(mock.reqs[0].Header, ShouldResemble, http.Header{ | 64 So(mock.reqs[0].Header, ShouldResemble, http.Header{ |
| 61 » » » » "Authorization": {"Bearer blah"}, | 65 » » » » "Authorization": {"Bearer blah:https://www.googl
eapis.com/auth/userinfo.email"}, |
| 62 }) | 66 }) |
| 63 }) | 67 }) |
| 64 | 68 |
| 65 Convey("in AsUser mode, authenticated", func(c C) { | 69 Convey("in AsUser mode, authenticated", func(c C) { |
| 66 ctx := WithState(ctx, &state{ | 70 ctx := WithState(ctx, &state{ |
| 67 user: &User{Identity: "user:abc@example.com"}, | 71 user: &User{Identity: "user:abc@example.com"}, |
| 68 }) | 72 }) |
| 69 | 73 |
| 70 t, err := GetRPCTransport(ctx, AsUser, &rpcMocks{ | 74 t, err := GetRPCTransport(ctx, AsUser, &rpcMocks{ |
| 71 MintDelegationToken: func(ic context.Context, p
DelegationTokenParams) (*delegation.Token, error) { | 75 MintDelegationToken: func(ic context.Context, p
DelegationTokenParams) (*delegation.Token, error) { |
| 72 c.So(p, ShouldResemble, DelegationTokenP
arams{ | 76 c.So(p, ShouldResemble, DelegationTokenP
arams{ |
| 73 TargetHost: "example.com", | 77 TargetHost: "example.com", |
| 74 MinTTL: 10 * time.Minute, | 78 MinTTL: 10 * time.Minute, |
| 75 }) | 79 }) |
| 76 return &delegation.Token{Token: "deleg_t
ok"}, nil | 80 return &delegation.Token{Token: "deleg_t
ok"}, nil |
| 77 }, | 81 }, |
| 78 }) | 82 }) |
| 79 So(err, ShouldBeNil) | 83 So(err, ShouldBeNil) |
| 80 _, err = t.RoundTrip(makeReq("https://example.com/some-p
ath/sd")) | 84 _, err = t.RoundTrip(makeReq("https://example.com/some-p
ath/sd")) |
| 81 So(err, ShouldBeNil) | 85 So(err, ShouldBeNil) |
| 82 | 86 |
| 83 So(mock.calls[0], ShouldResemble, []string{"https://www.
googleapis.com/auth/userinfo.email"}) | 87 So(mock.calls[0], ShouldResemble, []string{"https://www.
googleapis.com/auth/userinfo.email"}) |
| 84 So(mock.reqs[0].Header, ShouldResemble, http.Header{ | 88 So(mock.reqs[0].Header, ShouldResemble, http.Header{ |
| 85 » » » » "Authorization": {"Bearer blah"}, | 89 » » » » "Authorization": {"Bearer blah:https://w
ww.googleapis.com/auth/userinfo.email"}, |
| 86 "X-Delegation-Token-V1": {"deleg_tok"}, | 90 "X-Delegation-Token-V1": {"deleg_tok"}, |
| 87 }) | 91 }) |
| 88 }) | 92 }) |
| 89 | 93 |
| 90 Convey("in AsUser mode, anonymous", func(c C) { | 94 Convey("in AsUser mode, anonymous", func(c C) { |
| 91 ctx := WithState(ctx, &state{ | 95 ctx := WithState(ctx, &state{ |
| 92 user: &User{Identity: identity.AnonymousIdentity
}, | 96 user: &User{Identity: identity.AnonymousIdentity
}, |
| 93 }) | 97 }) |
| 94 | 98 |
| 95 t, err := GetRPCTransport(ctx, AsUser, &rpcMocks{ | 99 t, err := GetRPCTransport(ctx, AsUser, &rpcMocks{ |
| (...skipping 16 matching lines...) Expand all Loading... |
| 112 MintDelegationToken: func(ic context.Context, p
DelegationTokenParams) (*delegation.Token, error) { | 116 MintDelegationToken: func(ic context.Context, p
DelegationTokenParams) (*delegation.Token, error) { |
| 113 panic("must not be called") | 117 panic("must not be called") |
| 114 }, | 118 }, |
| 115 }) | 119 }) |
| 116 So(err, ShouldBeNil) | 120 So(err, ShouldBeNil) |
| 117 _, err = t.RoundTrip(makeReq("https://example.com")) | 121 _, err = t.RoundTrip(makeReq("https://example.com")) |
| 118 So(err, ShouldBeNil) | 122 So(err, ShouldBeNil) |
| 119 | 123 |
| 120 So(mock.calls[0], ShouldResemble, []string{"https://www.
googleapis.com/auth/userinfo.email"}) | 124 So(mock.calls[0], ShouldResemble, []string{"https://www.
googleapis.com/auth/userinfo.email"}) |
| 121 So(mock.reqs[0].Header, ShouldResemble, http.Header{ | 125 So(mock.reqs[0].Header, ShouldResemble, http.Header{ |
| 122 » » » » "Authorization": {"Bearer blah"}, | 126 » » » » "Authorization": {"Bearer blah:https://w
ww.googleapis.com/auth/userinfo.email"}, |
| 123 "X-Delegation-Token-V1": {"deleg_tok"}, | 127 "X-Delegation-Token-V1": {"deleg_tok"}, |
| 124 }) | 128 }) |
| 125 }) | 129 }) |
| 126 | 130 |
| 127 Convey("in NoAuth mode with scopes, should error", func(c C) { | 131 Convey("in NoAuth mode with scopes, should error", func(c C) { |
| 128 _, err := GetRPCTransport(ctx, NoAuth, WithScopes("A")) | 132 _, err := GetRPCTransport(ctx, NoAuth, WithScopes("A")) |
| 129 So(err, ShouldNotBeNil) | 133 So(err, ShouldNotBeNil) |
| 130 }) | 134 }) |
| 131 }) | 135 }) |
| 132 } | 136 } |
| 133 | 137 |
| 138 func TestTokenSource(t *testing.T) { |
| 139 t.Parallel() |
| 140 |
| 141 Convey("GetTokenSourceAsSelf works", t, func() { |
| 142 ctx := context.Background() |
| 143 mock := &clientRPCTransportMock{} |
| 144 ctx = ModifyConfig(ctx, func(cfg *Config) { |
| 145 cfg.AccessTokenProvider = mock.getAccessToken |
| 146 cfg.AnonymousTransport = mock.getTransport |
| 147 }) |
| 148 |
| 149 Convey("With no scopes", func() { |
| 150 ts := GetTokenSourceAsSelf(ctx) |
| 151 tok, err := ts.Token() |
| 152 So(err, ShouldBeNil) |
| 153 So(tok, ShouldResemble, &oauth2.Token{ |
| 154 AccessToken: "blah:https://www.googleapis.com/au
th/userinfo.email", |
| 155 TokenType: "Bearer", |
| 156 }) |
| 157 }) |
| 158 |
| 159 Convey("With a specific list of scopes", func() { |
| 160 ts := GetTokenSourceAsSelf(ctx, "foo", "bar", "baz") |
| 161 tok, err := ts.Token() |
| 162 So(err, ShouldBeNil) |
| 163 So(tok, ShouldResemble, &oauth2.Token{ |
| 164 AccessToken: "blah:foo,bar,baz", |
| 165 TokenType: "Bearer", |
| 166 }) |
| 167 }) |
| 168 }) |
| 169 } |
| 170 |
| 134 func makeReq(url string) *http.Request { | 171 func makeReq(url string) *http.Request { |
| 135 req, err := http.NewRequest("GET", url, nil) | 172 req, err := http.NewRequest("GET", url, nil) |
| 136 if err != nil { | 173 if err != nil { |
| 137 panic(err) | 174 panic(err) |
| 138 } | 175 } |
| 139 return req | 176 return req |
| 140 } | 177 } |
| 141 | 178 |
| 142 type clientRPCTransportMock struct { | 179 type clientRPCTransportMock struct { |
| 143 calls [][]string | 180 calls [][]string |
| 144 reqs []*http.Request | 181 reqs []*http.Request |
| 145 | 182 |
| 146 cb func(req *http.Request, body string) string | 183 cb func(req *http.Request, body string) string |
| 147 } | 184 } |
| 148 | 185 |
| 149 func (m *clientRPCTransportMock) getAccessToken(c context.Context, scopes []stri
ng) (auth.Token, error) { | 186 func (m *clientRPCTransportMock) getAccessToken(c context.Context, scopes []stri
ng) (auth.Token, error) { |
| 150 m.calls = append(m.calls, scopes) | 187 m.calls = append(m.calls, scopes) |
| 151 » return auth.Token{AccessToken: "blah", TokenType: "Bearer"}, nil | 188 » return auth.Token{AccessToken: "blah:" + strings.Join(scopes, ","), Toke
nType: "Bearer"}, nil |
| 152 } | 189 } |
| 153 | 190 |
| 154 func (m *clientRPCTransportMock) getTransport(c context.Context) http.RoundTripp
er { | 191 func (m *clientRPCTransportMock) getTransport(c context.Context) http.RoundTripp
er { |
| 155 return m | 192 return m |
| 156 } | 193 } |
| 157 | 194 |
| 158 func (m *clientRPCTransportMock) RoundTrip(req *http.Request) (*http.Response, e
rror) { | 195 func (m *clientRPCTransportMock) RoundTrip(req *http.Request) (*http.Response, e
rror) { |
| 159 m.reqs = append(m.reqs, req) | 196 m.reqs = append(m.reqs, req) |
| 160 code := 500 | 197 code := 500 |
| 161 resp := "internal error" | 198 resp := "internal error" |
| 162 if req.Body != nil { | 199 if req.Body != nil { |
| 163 body, err := ioutil.ReadAll(req.Body) | 200 body, err := ioutil.ReadAll(req.Body) |
| 164 req.Body.Close() | 201 req.Body.Close() |
| 165 if err != nil { | 202 if err != nil { |
| 166 return nil, err | 203 return nil, err |
| 167 } | 204 } |
| 168 if m.cb != nil { | 205 if m.cb != nil { |
| 169 code = 200 | 206 code = 200 |
| 170 resp = m.cb(req, string(body)) | 207 resp = m.cb(req, string(body)) |
| 171 } | 208 } |
| 172 } | 209 } |
| 173 return &http.Response{ | 210 return &http.Response{ |
| 174 StatusCode: code, | 211 StatusCode: code, |
| 175 Body: ioutil.NopCloser(bytes.NewReader([]byte(resp))), | 212 Body: ioutil.NopCloser(bytes.NewReader([]byte(resp))), |
| 176 }, nil | 213 }, nil |
| 177 } | 214 } |
| OLD | NEW |