| 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 openid | 5 package openid |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "fmt" | 8 "fmt" |
| 9 "net/http" | 9 "net/http" |
| 10 "net/http/httptest" | 10 "net/http/httptest" |
| 11 "net/url" | 11 "net/url" |
| 12 "testing" | 12 "testing" |
| 13 "time" | 13 "time" |
| 14 | 14 |
| 15 "github.com/luci/luci-go/common/clock/testclock" | 15 "github.com/luci/luci-go/common/clock/testclock" |
| 16 "github.com/luci/luci-go/server/auth" | 16 "github.com/luci/luci-go/server/auth" |
| 17 "github.com/luci/luci-go/server/auth/authtest" | 17 "github.com/luci/luci-go/server/auth/authtest" |
| 18 "github.com/luci/luci-go/server/router" |
| 18 "github.com/luci/luci-go/server/secrets/testsecrets" | 19 "github.com/luci/luci-go/server/secrets/testsecrets" |
| 19 "github.com/luci/luci-go/server/settings" | 20 "github.com/luci/luci-go/server/settings" |
| 20 "golang.org/x/net/context" | 21 "golang.org/x/net/context" |
| 21 | 22 |
| 22 . "github.com/luci/luci-go/common/testing/assertions" | 23 . "github.com/luci/luci-go/common/testing/assertions" |
| 23 . "github.com/smartystreets/goconvey/convey" | 24 . "github.com/smartystreets/goconvey/convey" |
| 24 ) | 25 ) |
| 25 | 26 |
| 26 func TestFullFlow(t *testing.T) { | 27 func TestFullFlow(t *testing.T) { |
| 27 Convey("with test context", t, func(c C) { | 28 Convey("with test context", t, func(c C) { |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 85 | 86 |
| 86 // Generate login URL. | 87 // Generate login URL. |
| 87 loginURL, err := method.LoginURL(ctx, "/destination") | 88 loginURL, err := method.LoginURL(ctx, "/destination") |
| 88 So(err, ShouldBeNil) | 89 So(err, ShouldBeNil) |
| 89 So(loginURL, ShouldEqual, "/auth/openid/login?r=%2Fdesti
nation") | 90 So(loginURL, ShouldEqual, "/auth/openid/login?r=%2Fdesti
nation") |
| 90 | 91 |
| 91 // "Visit" login URL. | 92 // "Visit" login URL. |
| 92 req, err := http.NewRequest("GET", "http://fake"+loginUR
L, nil) | 93 req, err := http.NewRequest("GET", "http://fake"+loginUR
L, nil) |
| 93 So(err, ShouldBeNil) | 94 So(err, ShouldBeNil) |
| 94 rec := httptest.NewRecorder() | 95 rec := httptest.NewRecorder() |
| 95 » » » method.loginHandler(ctx, rec, req, nil) | 96 » » » method.loginHandler(&router.Context{ |
| 97 » » » » Context: ctx, |
| 98 » » » » Writer: rec, |
| 99 » » » » Request: req, |
| 100 » » » }) |
| 96 | 101 |
| 97 // It asks us to visit authorizarion endpoint. | 102 // It asks us to visit authorizarion endpoint. |
| 98 So(rec.Code, ShouldEqual, http.StatusFound) | 103 So(rec.Code, ShouldEqual, http.StatusFound) |
| 99 parsed, err := url.Parse(rec.Header().Get("Location")) | 104 parsed, err := url.Parse(rec.Header().Get("Location")) |
| 100 So(err, ShouldBeNil) | 105 So(err, ShouldBeNil) |
| 101 So(parsed.Host, ShouldEqual, ts.URL[len("http://"):]) | 106 So(parsed.Host, ShouldEqual, ts.URL[len("http://"):]) |
| 102 So(parsed.Path, ShouldEqual, "/authorization") | 107 So(parsed.Path, ShouldEqual, "/authorization") |
| 103 So(parsed.Query(), ShouldResemble, url.Values{ | 108 So(parsed.Query(), ShouldResemble, url.Values{ |
| 104 "client_id": {"client_id"}, | 109 "client_id": {"client_id"}, |
| 105 "redirect_uri": {"http://fake/redirect"}, | 110 "redirect_uri": {"http://fake/redirect"}, |
| 106 "response_type": {"code"}, | 111 "response_type": {"code"}, |
| 107 "scope": {"openid email profile"}, | 112 "scope": {"openid email profile"}, |
| 108 "prompt": {"select_account"}, | 113 "prompt": {"select_account"}, |
| 109 "state": { | 114 "state": { |
| 110 "AXsiX2kiOiIxNDQyNTQwMDAwMDAwIiwiZGVzdF9
1cmwiOiIvZGVzdGluYXRpb24iLC" + | 115 "AXsiX2kiOiIxNDQyNTQwMDAwMDAwIiwiZGVzdF9
1cmwiOiIvZGVzdGluYXRpb24iLC" + |
| 111 "Job3N0X3VybCI6ImZha2UifUFtzG6wP
buvHG2mY_Wf6eQ_Eiu7n3_Tf6GmRcse1g" + | 116 "Job3N0X3VybCI6ImZha2UifUFtzG6wP
buvHG2mY_Wf6eQ_Eiu7n3_Tf6GmRcse1g" + |
| 112 "YE", | 117 "YE", |
| 113 }, | 118 }, |
| 114 }) | 119 }) |
| 115 | 120 |
| 116 // Pretend we've done it. OpenID redirects user's browse
r to callback URI. | 121 // Pretend we've done it. OpenID redirects user's browse
r to callback URI. |
| 117 // `callbackHandler` will call /token and /userinfo fake
endpoints exposed | 122 // `callbackHandler` will call /token and /userinfo fake
endpoints exposed |
| 118 // by testserver. | 123 // by testserver. |
| 119 callbackParams := url.Values{} | 124 callbackParams := url.Values{} |
| 120 callbackParams.Set("code", "omg_auth_code") | 125 callbackParams.Set("code", "omg_auth_code") |
| 121 callbackParams.Set("state", parsed.Query().Get("state")) | 126 callbackParams.Set("state", parsed.Query().Get("state")) |
| 122 req, err = http.NewRequest("GET", "http://fake/redirect?
"+callbackParams.Encode(), nil) | 127 req, err = http.NewRequest("GET", "http://fake/redirect?
"+callbackParams.Encode(), nil) |
| 123 So(err, ShouldBeNil) | 128 So(err, ShouldBeNil) |
| 124 rec = httptest.NewRecorder() | 129 rec = httptest.NewRecorder() |
| 125 » » » method.callbackHandler(ctx, rec, req, nil) | 130 » » » method.callbackHandler(&router.Context{ |
| 131 » » » » Context: ctx, |
| 132 » » » » Writer: rec, |
| 133 » » » » Request: req, |
| 134 » » » }) |
| 126 | 135 |
| 127 // We should be redirected to the login page, with sessi
on cookie set. | 136 // We should be redirected to the login page, with sessi
on cookie set. |
| 128 expectedCookie := "oid_session=AXsiX2kiOiIxNDQyNTQwMDAwM
DAwIiwic2lkIjoi" + | 137 expectedCookie := "oid_session=AXsiX2kiOiIxNDQyNTQwMDAwM
DAwIiwic2lkIjoi" + |
| 129 "dXNlcl9pZF9zdWIvMSJ9PmRzaOv-mS0PMHkve897iiELNmp
iLi_j3ICG1VKuNCs" | 138 "dXNlcl9pZF9zdWIvMSJ9PmRzaOv-mS0PMHkve897iiELNmp
iLi_j3ICG1VKuNCs" |
| 130 So(rec.Code, ShouldEqual, http.StatusFound) | 139 So(rec.Code, ShouldEqual, http.StatusFound) |
| 131 So(rec.Header().Get("Location"), ShouldEqual, "/destinat
ion") | 140 So(rec.Header().Get("Location"), ShouldEqual, "/destinat
ion") |
| 132 So(rec.Header().Get("Set-Cookie"), ShouldEqual, | 141 So(rec.Header().Get("Set-Cookie"), ShouldEqual, |
| 133 expectedCookie+"; Path=/; Expires=Sun, 18 Oct 20
15 01:18:20 GMT; Max-Age=2591100; HttpOnly") | 142 expectedCookie+"; Path=/; Expires=Sun, 18 Oct 20
15 01:18:20 GMT; Max-Age=2591100; HttpOnly") |
| 134 | 143 |
| 135 // Use the cookie to authenticate some call. | 144 // Use the cookie to authenticate some call. |
| (...skipping 10 matching lines...) Expand all Loading... |
| 146 }) | 155 }) |
| 147 | 156 |
| 148 // Now generate URL to and visit logout page. | 157 // Now generate URL to and visit logout page. |
| 149 logoutURL, err := method.LogoutURL(ctx, "/another_destin
ation") | 158 logoutURL, err := method.LogoutURL(ctx, "/another_destin
ation") |
| 150 So(err, ShouldBeNil) | 159 So(err, ShouldBeNil) |
| 151 So(logoutURL, ShouldEqual, "/auth/openid/logout?r=%2Fano
ther_destination") | 160 So(logoutURL, ShouldEqual, "/auth/openid/logout?r=%2Fano
ther_destination") |
| 152 req, err = http.NewRequest("GET", "http://fake"+logoutUR
L, nil) | 161 req, err = http.NewRequest("GET", "http://fake"+logoutUR
L, nil) |
| 153 So(err, ShouldBeNil) | 162 So(err, ShouldBeNil) |
| 154 req.Header.Add("Cookie", expectedCookie) | 163 req.Header.Add("Cookie", expectedCookie) |
| 155 rec = httptest.NewRecorder() | 164 rec = httptest.NewRecorder() |
| 156 » » » method.logoutHandler(ctx, rec, req, nil) | 165 » » » method.logoutHandler(&router.Context{ |
| 166 » » » » Context: ctx, |
| 167 » » » » Writer: rec, |
| 168 » » » » Request: req, |
| 169 » » » }) |
| 157 | 170 |
| 158 // Should be redirected to destination with the cookie k
illed. | 171 // Should be redirected to destination with the cookie k
illed. |
| 159 So(rec.Code, ShouldEqual, http.StatusFound) | 172 So(rec.Code, ShouldEqual, http.StatusFound) |
| 160 So(rec.Header().Get("Location"), ShouldEqual, "/another_
destination") | 173 So(rec.Header().Get("Location"), ShouldEqual, "/another_
destination") |
| 161 So(rec.Header().Get("Set-Cookie"), ShouldEqual, | 174 So(rec.Header().Get("Set-Cookie"), ShouldEqual, |
| 162 "oid_session=deleted; Path=/; Expires=Thu, 01 Ja
n 1970 00:00:01 GMT; Max-Age=0") | 175 "oid_session=deleted; Path=/; Expires=Thu, 01 Ja
n 1970 00:00:01 GMT; Max-Age=0") |
| 163 }) | 176 }) |
| 164 }) | 177 }) |
| 165 } | 178 } |
| 166 | 179 |
| 167 func TestCallbackHandleEdgeCases(t *testing.T) { | 180 func TestCallbackHandleEdgeCases(t *testing.T) { |
| 168 Convey("with test context", t, func(c C) { | 181 Convey("with test context", t, func(c C) { |
| 169 ctx := context.Background() | 182 ctx := context.Background() |
| 170 ctx = settings.Use(ctx, settings.New(&settings.MemoryStorage{})) | 183 ctx = settings.Use(ctx, settings.New(&settings.MemoryStorage{})) |
| 171 ctx, _ = testclock.UseTime(ctx, time.Unix(1442540000, 0)) | 184 ctx, _ = testclock.UseTime(ctx, time.Unix(1442540000, 0)) |
| 172 ctx = testsecrets.Use(ctx) | 185 ctx = testsecrets.Use(ctx) |
| 173 | 186 |
| 174 method := AuthMethod{SessionStore: &authtest.MemorySessionStore{
}} | 187 method := AuthMethod{SessionStore: &authtest.MemorySessionStore{
}} |
| 175 | 188 |
| 176 call := func(query map[string]string) *httptest.ResponseRecorder
{ | 189 call := func(query map[string]string) *httptest.ResponseRecorder
{ |
| 177 q := url.Values{} | 190 q := url.Values{} |
| 178 for k, v := range query { | 191 for k, v := range query { |
| 179 q.Add(k, v) | 192 q.Add(k, v) |
| 180 } | 193 } |
| 181 req, err := http.NewRequest("GET", "/auth/openid/callbac
k?"+q.Encode(), nil) | 194 req, err := http.NewRequest("GET", "/auth/openid/callbac
k?"+q.Encode(), nil) |
| 182 c.So(err, ShouldBeNil) | 195 c.So(err, ShouldBeNil) |
| 183 req.Host = "fake.com" | 196 req.Host = "fake.com" |
| 184 rec := httptest.NewRecorder() | 197 rec := httptest.NewRecorder() |
| 185 » » » method.callbackHandler(ctx, rec, req, nil) | 198 » » » method.callbackHandler(&router.Context{ |
| 199 » » » » Context: ctx, |
| 200 » » » » Writer: rec, |
| 201 » » » » Request: req, |
| 202 » » » }) |
| 186 return rec | 203 return rec |
| 187 } | 204 } |
| 188 | 205 |
| 189 Convey("handles 'error'", func() { | 206 Convey("handles 'error'", func() { |
| 190 rec := call(map[string]string{"error": "Omg, error"}) | 207 rec := call(map[string]string{"error": "Omg, error"}) |
| 191 So(rec.Code, ShouldEqual, 400) | 208 So(rec.Code, ShouldEqual, 400) |
| 192 So(rec.Body.String(), ShouldEqual, "OpenID login error:
Omg, error\n") | 209 So(rec.Body.String(), ShouldEqual, "OpenID login error:
Omg, error\n") |
| 193 }) | 210 }) |
| 194 | 211 |
| 195 Convey("handles no 'code'", func() { | 212 Convey("handles no 'code'", func() { |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 250 ctx := context.Background() | 267 ctx := context.Background() |
| 251 method := AuthMethod{SessionStore: &authtest.MemorySessionStore{
}} | 268 method := AuthMethod{SessionStore: &authtest.MemorySessionStore{
}} |
| 252 | 269 |
| 253 _, err := method.LoginURL(ctx, "http://somesite") | 270 _, err := method.LoginURL(ctx, "http://somesite") |
| 254 So(err, ShouldErrLike, "openid: dest URL in LoginURL or LogoutUR
L must be relative") | 271 So(err, ShouldErrLike, "openid: dest URL in LoginURL or LogoutUR
L must be relative") |
| 255 | 272 |
| 256 _, err = method.LogoutURL(ctx, "http://somesite") | 273 _, err = method.LogoutURL(ctx, "http://somesite") |
| 257 So(err, ShouldErrLike, "openid: dest URL in LoginURL or LogoutUR
L must be relative") | 274 So(err, ShouldErrLike, "openid: dest URL in LoginURL or LogoutUR
L must be relative") |
| 258 }) | 275 }) |
| 259 } | 276 } |
| OLD | NEW |