| OLD | NEW |
| 1 // Copyright 2015 The LUCI Authors. All rights reserved. | 1 // Copyright 2015 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 » "errors" | 8 » "fmt" |
| 9 "net" | 9 "net" |
| 10 "net/http" | 10 "net/http" |
| 11 "net/http/httptest" |
| 11 "testing" | 12 "testing" |
| 12 | 13 |
| 13 "golang.org/x/net/context" | 14 "golang.org/x/net/context" |
| 14 | 15 |
| 16 "github.com/luci/luci-go/server/router" |
| 15 "github.com/luci/luci-go/server/secrets" | 17 "github.com/luci/luci-go/server/secrets" |
| 16 | 18 |
| 17 "github.com/luci/luci-go/server/auth/authdb" | 19 "github.com/luci/luci-go/server/auth/authdb" |
| 18 "github.com/luci/luci-go/server/auth/identity" | 20 "github.com/luci/luci-go/server/auth/identity" |
| 19 "github.com/luci/luci-go/server/auth/service/protocol" | 21 "github.com/luci/luci-go/server/auth/service/protocol" |
| 20 "github.com/luci/luci-go/server/auth/signing" | 22 "github.com/luci/luci-go/server/auth/signing" |
| 21 | 23 |
| 24 "github.com/luci/luci-go/common/errors" |
| 22 . "github.com/luci/luci-go/common/testing/assertions" | 25 . "github.com/luci/luci-go/common/testing/assertions" |
| 23 . "github.com/smartystreets/goconvey/convey" | 26 . "github.com/smartystreets/goconvey/convey" |
| 24 ) | 27 ) |
| 25 | 28 |
| 26 func TestAuthenticate(t *testing.T) { | 29 func TestAuthenticate(t *testing.T) { |
| 27 t.Parallel() | 30 t.Parallel() |
| 28 | 31 |
| 29 » Convey("IsAllowedOAuthClientID on default DB", t, func() { | 32 » Convey("Happy path", t, func() { |
| 30 » » c := context.Background() | |
| 31 » » auth := Authenticator{fakeOAuthMethod{clientID: "some_client_id"
}} | |
| 32 » » _, err := auth.Authenticate(c, makeRequest()) | |
| 33 » » So(err, ShouldErrLike, "the library is not properly configured") | |
| 34 » }) | |
| 35 | |
| 36 » Convey("IsAllowedOAuthClientID with valid client_id", t, func() { | |
| 37 c := injectTestDB(context.Background(), &fakeDB{ | 33 c := injectTestDB(context.Background(), &fakeDB{ |
| 38 allowedClientID: "some_client_id", | 34 allowedClientID: "some_client_id", |
| 39 }) | 35 }) |
| 40 » » auth := Authenticator{fakeOAuthMethod{clientID: "some_client_id"
}} | 36 » » auth := Authenticator{ |
| 37 » » » Methods: []Method{fakeAuthMethod{clientID: "some_client_
id"}}, |
| 38 » » } |
| 39 » » c, err := auth.Authenticate(c, makeRequest()) |
| 40 » » So(err, ShouldBeNil) |
| 41 |
| 42 » » So(CurrentUser(c), ShouldResemble, &User{ |
| 43 » » » Identity: "user:abc@example.com", |
| 44 » » » Email: "abc@example.com", |
| 45 » » » ClientID: "some_client_id", |
| 46 » » }) |
| 47 |
| 48 » » url, err := LoginURL(c, "login") |
| 49 » » So(err, ShouldBeNil) |
| 50 » » So(url, ShouldEqual, "http://fake.login.url/login") |
| 51 |
| 52 » » url, err = LogoutURL(c, "logout") |
| 53 » » So(err, ShouldBeNil) |
| 54 » » So(url, ShouldEqual, "http://fake.logout.url/logout") |
| 55 » }) |
| 56 |
| 57 » Convey("No methods given", t, func() { |
| 58 » » c := injectTestDB(context.Background(), &fakeDB{ |
| 59 » » » allowedClientID: "some_client_id", |
| 60 » » }) |
| 61 » » auth := Authenticator{} |
| 41 _, err := auth.Authenticate(c, makeRequest()) | 62 _, err := auth.Authenticate(c, makeRequest()) |
| 42 » » So(err, ShouldBeNil) | 63 » » So(err, ShouldEqual, ErrNotConfigured) |
| 64 » }) |
| 65 |
| 66 » Convey("IsAllowedOAuthClientID on default DB", t, func() { |
| 67 » » c := context.Background() |
| 68 » » auth := Authenticator{ |
| 69 » » » Methods: []Method{fakeAuthMethod{clientID: "some_client_
id"}}, |
| 70 » » } |
| 71 » » _, err := auth.Authenticate(c, makeRequest()) |
| 72 » » So(err, ShouldErrLike, "the library is not properly configured") |
| 43 }) | 73 }) |
| 44 | 74 |
| 45 Convey("IsAllowedOAuthClientID with invalid client_id", t, func() { | 75 Convey("IsAllowedOAuthClientID with invalid client_id", t, func() { |
| 46 c := injectTestDB(context.Background(), &fakeDB{ | 76 c := injectTestDB(context.Background(), &fakeDB{ |
| 47 allowedClientID: "some_client_id", | 77 allowedClientID: "some_client_id", |
| 48 }) | 78 }) |
| 49 » » auth := Authenticator{fakeOAuthMethod{clientID: "another_client_
id"}} | 79 » » auth := Authenticator{ |
| 80 » » » Methods: []Method{fakeAuthMethod{clientID: "another_clie
nt_id"}}, |
| 81 » » } |
| 50 _, err := auth.Authenticate(c, makeRequest()) | 82 _, err := auth.Authenticate(c, makeRequest()) |
| 51 So(err, ShouldEqual, ErrBadClientID) | 83 So(err, ShouldEqual, ErrBadClientID) |
| 52 }) | 84 }) |
| 53 | 85 |
| 54 Convey("IP whitelist restriction works", t, func() { | 86 Convey("IP whitelist restriction works", t, func() { |
| 55 db, err := authdb.NewSnapshotDB(&protocol.AuthDB{ | 87 db, err := authdb.NewSnapshotDB(&protocol.AuthDB{ |
| 56 IpWhitelistAssignments: []*protocol.AuthIPWhitelistAssig
nment{ | 88 IpWhitelistAssignments: []*protocol.AuthIPWhitelistAssig
nment{ |
| 57 { | 89 { |
| 58 Identity: strPtr("user:abc@example.co
m"), | 90 Identity: strPtr("user:abc@example.co
m"), |
| 59 IpWhitelist: strPtr("whitelist"), | 91 IpWhitelist: strPtr("whitelist"), |
| 60 }, | 92 }, |
| 61 }, | 93 }, |
| 62 IpWhitelists: []*protocol.AuthIPWhitelist{ | 94 IpWhitelists: []*protocol.AuthIPWhitelist{ |
| 63 { | 95 { |
| 64 Name: strPtr("whitelist"), | 96 Name: strPtr("whitelist"), |
| 65 Subnets: []string{ | 97 Subnets: []string{ |
| 66 "1.2.3.4/32", | 98 "1.2.3.4/32", |
| 67 }, | 99 }, |
| 68 }, | 100 }, |
| 69 }, | 101 }, |
| 70 }, "http://auth-service", 1234) | 102 }, "http://auth-service", 1234) |
| 71 So(err, ShouldBeNil) | 103 So(err, ShouldBeNil) |
| 72 | 104 |
| 73 c := injectTestDB(context.Background(), db) | 105 c := injectTestDB(context.Background(), db) |
| 74 | 106 |
| 75 Convey("User is using IP whitelist and IP is in the whitelist.",
func() { | 107 Convey("User is using IP whitelist and IP is in the whitelist.",
func() { |
| 76 » » » auth := Authenticator{fakeOAuthMethod{email: "abc@exampl
e.com"}} | 108 » » » auth := Authenticator{ |
| 109 » » » » Methods: []Method{fakeAuthMethod{email: "abc@exa
mple.com"}}, |
| 110 » » » } |
| 77 req := makeRequest() | 111 req := makeRequest() |
| 78 req.RemoteAddr = "1.2.3.4" | 112 req.RemoteAddr = "1.2.3.4" |
| 79 c, err := auth.Authenticate(c, req) | 113 c, err := auth.Authenticate(c, req) |
| 80 So(err, ShouldBeNil) | 114 So(err, ShouldBeNil) |
| 81 So(CurrentIdentity(c), ShouldEqual, identity.Identity("u
ser:abc@example.com")) | 115 So(CurrentIdentity(c), ShouldEqual, identity.Identity("u
ser:abc@example.com")) |
| 82 }) | 116 }) |
| 83 | 117 |
| 84 Convey("User is using IP whitelist and IP is NOT in the whitelis
t.", func() { | 118 Convey("User is using IP whitelist and IP is NOT in the whitelis
t.", func() { |
| 85 » » » auth := Authenticator{fakeOAuthMethod{email: "abc@exampl
e.com"}} | 119 » » » auth := Authenticator{ |
| 120 » » » » Methods: []Method{fakeAuthMethod{email: "abc@exa
mple.com"}}, |
| 121 » » » } |
| 86 req := makeRequest() | 122 req := makeRequest() |
| 87 req.RemoteAddr = "1.2.3.5" | 123 req.RemoteAddr = "1.2.3.5" |
| 88 _, err := auth.Authenticate(c, req) | 124 _, err := auth.Authenticate(c, req) |
| 89 So(err, ShouldEqual, ErrIPNotWhitelisted) | 125 So(err, ShouldEqual, ErrIPNotWhitelisted) |
| 90 }) | 126 }) |
| 91 | 127 |
| 92 Convey("User is not using IP whitelist.", func() { | 128 Convey("User is not using IP whitelist.", func() { |
| 93 » » » auth := Authenticator{fakeOAuthMethod{email: "def@exampl
e.com"}} | 129 » » » auth := Authenticator{ |
| 130 » » » » Methods: []Method{fakeAuthMethod{email: "def@exa
mple.com"}}, |
| 131 » » » } |
| 94 req := makeRequest() | 132 req := makeRequest() |
| 95 req.RemoteAddr = "1.2.3.5" | 133 req.RemoteAddr = "1.2.3.5" |
| 96 c, err := auth.Authenticate(c, req) | 134 c, err := auth.Authenticate(c, req) |
| 97 So(err, ShouldBeNil) | 135 So(err, ShouldBeNil) |
| 98 So(CurrentIdentity(c), ShouldEqual, identity.Identity("u
ser:def@example.com")) | 136 So(CurrentIdentity(c), ShouldEqual, identity.Identity("u
ser:def@example.com")) |
| 99 }) | 137 }) |
| 100 }) | 138 }) |
| 101 } | 139 } |
| 102 | 140 |
| 141 func TestMiddleware(t *testing.T) { |
| 142 t.Parallel() |
| 143 |
| 144 handler := func(c *router.Context) { |
| 145 fmt.Fprintf(c.Writer, "%s", CurrentIdentity(c.Context)) |
| 146 } |
| 147 |
| 148 call := func(a *Authenticator) *httptest.ResponseRecorder { |
| 149 req, err := http.NewRequest("GET", "http://example.com/foo", nil
) |
| 150 So(err, ShouldBeNil) |
| 151 w := httptest.NewRecorder() |
| 152 router.RunMiddleware(&router.Context{ |
| 153 Context: injectTestDB(context.Background(), &fakeDB{ |
| 154 allowedClientID: "some_client_id", |
| 155 }), |
| 156 Writer: w, |
| 157 Request: req, |
| 158 }, router.NewMiddlewareChain(a.GetMiddleware()), handler) |
| 159 return w |
| 160 } |
| 161 |
| 162 Convey("Happy path", t, func() { |
| 163 rr := call(&Authenticator{ |
| 164 Methods: []Method{fakeAuthMethod{clientID: "some_client_
id"}}, |
| 165 }) |
| 166 So(rr.Code, ShouldEqual, 200) |
| 167 So(rr.Body.String(), ShouldEqual, "user:abc@example.com") |
| 168 }) |
| 169 |
| 170 Convey("Fatal error", t, func() { |
| 171 rr := call(&Authenticator{ |
| 172 Methods: []Method{fakeAuthMethod{clientID: "another_clie
nt_id"}}, |
| 173 }) |
| 174 So(rr.Code, ShouldEqual, 401) |
| 175 So(rr.Body.String(), ShouldEqual, "Authentication error\n") |
| 176 }) |
| 177 |
| 178 Convey("Transient error", t, func() { |
| 179 rr := call(&Authenticator{ |
| 180 Methods: []Method{fakeAuthMethod{err: errors.WrapTransie
nt(errors.New("boo"))}}, |
| 181 }) |
| 182 So(rr.Code, ShouldEqual, 500) |
| 183 So(rr.Body.String(), ShouldEqual, "Transient error during authen
tication\n") |
| 184 }) |
| 185 } |
| 186 |
| 103 /// | 187 /// |
| 104 | 188 |
| 105 func makeRequest() *http.Request { | 189 func makeRequest() *http.Request { |
| 106 req, _ := http.NewRequest("GET", "http://some-url", nil) | 190 req, _ := http.NewRequest("GET", "http://some-url", nil) |
| 107 return req | 191 return req |
| 108 } | 192 } |
| 109 | 193 |
| 110 /// | 194 /// |
| 111 | 195 |
| 112 // fakeOAuthMethod implements Method. | 196 // fakeAuthMethod implements Method. |
| 113 type fakeOAuthMethod struct { | 197 type fakeAuthMethod struct { |
| 198 » err error |
| 114 clientID string | 199 clientID string |
| 115 email string | 200 email string |
| 116 } | 201 } |
| 117 | 202 |
| 118 func (m fakeOAuthMethod) Authenticate(context.Context, *http.Request) (*User, er
ror) { | 203 func (m fakeAuthMethod) Authenticate(context.Context, *http.Request) (*User, err
or) { |
| 204 » if m.err != nil { |
| 205 » » return nil, m.err |
| 206 » } |
| 119 email := m.email | 207 email := m.email |
| 120 if email == "" { | 208 if email == "" { |
| 121 email = "abc@example.com" | 209 email = "abc@example.com" |
| 122 } | 210 } |
| 123 return &User{ | 211 return &User{ |
| 124 Identity: identity.Identity("user:" + email), | 212 Identity: identity.Identity("user:" + email), |
| 125 Email: email, | 213 Email: email, |
| 126 ClientID: m.clientID, | 214 ClientID: m.clientID, |
| 127 }, nil | 215 }, nil |
| 128 } | 216 } |
| 129 | 217 |
| 218 func (m fakeAuthMethod) LoginURL(c context.Context, dest string) (string, error)
{ |
| 219 return "http://fake.login.url/" + dest, nil |
| 220 } |
| 221 |
| 222 func (m fakeAuthMethod) LogoutURL(c context.Context, dest string) (string, error
) { |
| 223 return "http://fake.logout.url/" + dest, nil |
| 224 } |
| 225 |
| 130 func injectTestDB(c context.Context, d authdb.DB) context.Context { | 226 func injectTestDB(c context.Context, d authdb.DB) context.Context { |
| 131 return SetConfig(c, Config{ | 227 return SetConfig(c, Config{ |
| 132 DBProvider: func(c context.Context) (authdb.DB, error) { | 228 DBProvider: func(c context.Context) (authdb.DB, error) { |
| 133 return d, nil | 229 return d, nil |
| 134 }, | 230 }, |
| 135 }) | 231 }) |
| 136 } | 232 } |
| 137 | 233 |
| 138 func strPtr(s string) *string { return &s } | 234 func strPtr(s string) *string { return &s } |
| 139 | 235 |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 176 } | 272 } |
| 177 return db.authServiceURL, nil | 273 return db.authServiceURL, nil |
| 178 } | 274 } |
| 179 | 275 |
| 180 func (db *fakeDB) GetTokenServiceURL(c context.Context) (string, error) { | 276 func (db *fakeDB) GetTokenServiceURL(c context.Context) (string, error) { |
| 181 if db.tokenServiceURL == "" { | 277 if db.tokenServiceURL == "" { |
| 182 return "", errors.New("fakeDB: GetTokenServiceURL is not configu
red") | 278 return "", errors.New("fakeDB: GetTokenServiceURL is not configu
red") |
| 183 } | 279 } |
| 184 return db.tokenServiceURL, nil | 280 return db.tokenServiceURL, nil |
| 185 } | 281 } |
| OLD | NEW |