OLD | NEW |
| (Empty) |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 package auth | |
6 | |
7 import ( | |
8 "io/ioutil" | |
9 "net/http" | |
10 "os" | |
11 "path/filepath" | |
12 "testing" | |
13 | |
14 "infra/libs/auth/internal" | |
15 "infra/libs/logging" | |
16 | |
17 "golang.org/x/net/context" | |
18 | |
19 . "github.com/smartystreets/goconvey/convey" | |
20 ) | |
21 | |
22 var ( | |
23 ctx = context.Background() | |
24 log = logging.Null() | |
25 ) | |
26 | |
27 func ExampleDefaultAuthenticatedClient() { | |
28 client, err := AuthenticatedClient(SilentLogin, NewAuthenticator(Options
{})) | |
29 if err == ErrLoginRequired { | |
30 log.Errorf("Run 'auth login' to login") | |
31 return | |
32 } | |
33 if err != nil { | |
34 log.Errorf("Failed to login: %s", err) | |
35 return | |
36 } | |
37 client.Get("https://some-server.appspot.com") | |
38 } | |
39 | |
40 func mockSecretsDir() string { | |
41 tempDir, err := ioutil.TempDir("", "auth_test") | |
42 So(err, ShouldBeNil) | |
43 | |
44 prev := secretsDir | |
45 secretsDir = func() string { return tempDir } | |
46 Reset(func() { | |
47 secretsDir = prev | |
48 os.RemoveAll(tempDir) | |
49 }) | |
50 So(SecretsDir(), ShouldEqual, tempDir) | |
51 | |
52 return tempDir | |
53 } | |
54 | |
55 func mockTokenProvider(factory func() internal.TokenProvider) { | |
56 prev := makeTokenProvider | |
57 makeTokenProvider = func(*Options) (internal.TokenProvider, error) { | |
58 return factory(), nil | |
59 } | |
60 Reset(func() { | |
61 makeTokenProvider = prev | |
62 }) | |
63 } | |
64 | |
65 func TestAuthenticator(t *testing.T) { | |
66 Convey("Given mocked secrets dir", t, func() { | |
67 tempDir := mockSecretsDir() | |
68 | |
69 Convey("Check NewAuthenticator defaults", func() { | |
70 clientID, clientSecret := DefaultClient() | |
71 ctx := context.Background() | |
72 a := NewAuthenticator(Options{Context: ctx}).(*authentic
atorImpl) | |
73 So(a.opts, ShouldResemble, &Options{ | |
74 Method: AutoSelectMethod, | |
75 Scopes: []string{OAuthScopeEmail
}, | |
76 ClientID: clientID, | |
77 ClientSecret: clientSecret, | |
78 ServiceAccountJSONPath: filepath.Join(tempDir, "
service_account.json"), | |
79 GCEAccountName: "default", | |
80 Context: ctx, | |
81 Logger: logging.Get(ctx), | |
82 }) | |
83 }) | |
84 }) | |
85 } | |
86 | |
87 func TestAuthenticatedClient(t *testing.T) { | |
88 Convey("Given mocked secrets dir", t, func() { | |
89 var tokenProvider internal.TokenProvider | |
90 | |
91 mockSecretsDir() | |
92 mockTokenProvider(func() internal.TokenProvider { return tokenPr
ovider }) | |
93 | |
94 Convey("Test login required", func() { | |
95 tokenProvider = &fakeTokenProvider{interactive: true} | |
96 c, err := AuthenticatedClient(InteractiveLogin, NewAuthe
nticator(Options{})) | |
97 So(err, ShouldBeNil) | |
98 So(c, ShouldNotEqual, http.DefaultClient) | |
99 }) | |
100 | |
101 Convey("Test login not required", func() { | |
102 tokenProvider = &fakeTokenProvider{interactive: true} | |
103 c, err := AuthenticatedClient(OptionalLogin, NewAuthenti
cator(Options{})) | |
104 So(err, ShouldBeNil) | |
105 So(c, ShouldEqual, http.DefaultClient) | |
106 }) | |
107 }) | |
108 } | |
109 | |
110 func TestRefreshToken(t *testing.T) { | |
111 Convey("Given mocked secrets dir", t, func() { | |
112 var tokenProvider *fakeTokenProvider | |
113 | |
114 mockSecretsDir() | |
115 mockTokenProvider(func() internal.TokenProvider { return tokenPr
ovider }) | |
116 | |
117 Convey("Test non interactive auth", func() { | |
118 tokenProvider = &fakeTokenProvider{ | |
119 interactive: false, | |
120 tokenToMint: &fakeToken{}, | |
121 } | |
122 auth, ok := NewAuthenticator(Options{}).(*authenticatorI
mpl) | |
123 So(ok, ShouldBeTrue) | |
124 _, err := auth.Transport() | |
125 So(err, ShouldBeNil) | |
126 // No token yet. The token is minted on first refresh. | |
127 So(auth.currentToken(), ShouldBeNil) | |
128 tok, err := auth.refreshToken(nil) | |
129 So(err, ShouldBeNil) | |
130 So(tok, ShouldEqual, tokenProvider.tokenToMint) | |
131 }) | |
132 | |
133 Convey("Test interactive auth (cache expired)", func() { | |
134 tokenProvider = &fakeTokenProvider{ | |
135 interactive: true, | |
136 tokenToMint: &fakeToken{name: "minted"}, | |
137 tokenToRefresh: &fakeToken{name: "refreshed"}, | |
138 tokenToUnmarshal: &fakeToken{name: "cached", exp
ired: true}, | |
139 } | |
140 auth, ok := NewAuthenticator(Options{}).(*authenticatorI
mpl) | |
141 So(ok, ShouldBeTrue) | |
142 _, err := auth.Transport() | |
143 So(err, ShouldEqual, ErrLoginRequired) | |
144 err = auth.Login() | |
145 So(err, ShouldBeNil) | |
146 _, err = auth.Transport() | |
147 So(err, ShouldBeNil) | |
148 // Minted initial token. | |
149 So(auth.currentToken(), ShouldEqual, tokenProvider.token
ToMint) | |
150 // Should return refreshed token. | |
151 tok, err := auth.refreshToken(auth.currentToken()) | |
152 So(err, ShouldBeNil) | |
153 So(tok, ShouldEqual, tokenProvider.tokenToRefresh) | |
154 }) | |
155 | |
156 Convey("Test interactive auth (cache non expired)", func() { | |
157 tokenProvider = &fakeTokenProvider{ | |
158 interactive: true, | |
159 tokenToMint: &fakeToken{name: "minted"}, | |
160 tokenToRefresh: &fakeToken{name: "refreshed"}, | |
161 tokenToUnmarshal: &fakeToken{name: "cached", exp
ired: false}, | |
162 } | |
163 auth, ok := NewAuthenticator(Options{}).(*authenticatorI
mpl) | |
164 So(ok, ShouldBeTrue) | |
165 _, err := auth.Transport() | |
166 So(err, ShouldEqual, ErrLoginRequired) | |
167 err = auth.Login() | |
168 So(err, ShouldBeNil) | |
169 _, err = auth.Transport() | |
170 So(err, ShouldBeNil) | |
171 // Minted initial token. | |
172 So(auth.currentToken(), ShouldEqual, tokenProvider.token
ToMint) | |
173 // Should return token from cache (since it's not expire
d yet). | |
174 tok, err := auth.refreshToken(auth.currentToken()) | |
175 So(err, ShouldBeNil) | |
176 So(tok, ShouldEqual, tokenProvider.tokenToUnmarshal) | |
177 }) | |
178 }) | |
179 } | |
180 | |
181 //////////////////////////////////////////////////////////////////////////////// | |
182 | |
183 type fakeTokenProvider struct { | |
184 interactive bool | |
185 tokenToMint internal.Token | |
186 tokenToRefresh internal.Token | |
187 tokenToUnmarshal internal.Token | |
188 } | |
189 | |
190 func (p *fakeTokenProvider) RequiresInteraction() bool { | |
191 return p.interactive | |
192 } | |
193 | |
194 func (p *fakeTokenProvider) MintToken() (internal.Token, error) { | |
195 if p.tokenToMint != nil { | |
196 return p.tokenToMint, nil | |
197 } | |
198 return &fakeToken{}, nil | |
199 } | |
200 | |
201 func (p *fakeTokenProvider) RefreshToken(internal.Token) (internal.Token, error)
{ | |
202 if p.tokenToRefresh != nil { | |
203 return p.tokenToRefresh, nil | |
204 } | |
205 return &fakeToken{}, nil | |
206 } | |
207 | |
208 func (p *fakeTokenProvider) MarshalToken(internal.Token) ([]byte, error) { | |
209 return []byte("fake token"), nil | |
210 } | |
211 | |
212 func (p *fakeTokenProvider) UnmarshalToken([]byte) (internal.Token, error) { | |
213 if p.tokenToUnmarshal != nil { | |
214 return p.tokenToUnmarshal, nil | |
215 } | |
216 return &fakeToken{}, nil | |
217 } | |
218 | |
219 type fakeToken struct { | |
220 name string | |
221 expired bool | |
222 } | |
223 | |
224 func (t *fakeToken) Equals(another internal.Token) bool { | |
225 casted, ok := another.(*fakeToken) | |
226 return ok && casted == t | |
227 } | |
228 | |
229 func (t *fakeToken) RequestHeaders() map[string]string { return make(map[string]
string) } | |
230 func (t *fakeToken) Expired() bool { return t.expired } | |
OLD | NEW |