// 正常に認証 URI にリダイレクトさせることの検査。 // X-Auth-Uri が ID プロバイダなら response_type を code にすることの検査。 // scope を openid にすることの検査。 // client_id, redirect_uri, state, nonce が追加されることの検査。 // セッションが発行されることの検査。 func TestAuth(t *testing.T) { // //////////////////////////////// // logutil.SetupConsole(logRoot, level.ALL) // defer logutil.SetupConsole(logRoot, level.OFF) // //////////////////////////////// page := newTestPage([]jwk.Key{test_taKey}, []idpdb.Element{test_idp}) r, err := newAuthRequest() if err != nil { t.Fatal(err) } w := httptest.NewRecorder() page.HandleAuth(w, r) if w.Code != http.StatusFound { t.Error(w.Code) t.Fatal(http.StatusFound) } else if uri, err := url.Parse(w.HeaderMap.Get("Location")); err != nil { t.Fatal(err) } else if authUri := uri.Scheme + "://" + uri.Host + uri.Path; authUri != test_idp.AuthUri() { t.Error(authUri) t.Fatal(test_idp.AuthUri()) } else if ok, err := regexp.MatchString(page.sessLabel+"=[0-9a-zA-Z_\\-]", w.HeaderMap.Get("Set-Cookie")); err != nil { t.Fatal(err) } else if !ok { t.Error("no new session") t.Fatal(w.HeaderMap.Get("Set-Cookie")) } else if q := uri.Query(); len(q) == 0 { t.Fatal("no query") } else if respType := request.FormValueSet(q.Get("response_type")); len(respType) != 1 || !respType["code"] { t.Error(respType) t.Fatal("code") } else if scop := request.FormValueSet(q.Get("scope")); len(scop) != 1 || !scop["openid"] { t.Error(scop) t.Fatal("openid") } else if taId := q.Get("client_id"); taId != page.selfId { t.Error(taId) t.Fatal(page.selfId) } else if rediUri := q.Get("redirect_uri"); rediUri != page.rediUri { t.Error(rediUri) t.Fatal(page.rediUri) } else if q.Get("state") == "" { t.Fatal("no state") } else if q.Get("nonce") == "" { t.Fatal("no nonce") } }
// X-Auth-Uri が ID プロバイダでないなら response_type を code id_token にすることの検査。 func TestAuthForSelector(t *testing.T) { // //////////////////////////////// // logutil.SetupConsole(logRoot, level.ALL) // defer logutil.SetupConsole(logRoot, level.OFF) // //////////////////////////////// page := newTestPage([]jwk.Key{test_taKey}, []idpdb.Element{test_idp}) r, err := newAuthRequest() if err != nil { t.Fatal(err) } r.Header.Set("X-Auth-Uri", test_idp.Id()+"a/auth") w := httptest.NewRecorder() page.HandleAuth(w, r) if w.Code != http.StatusFound { t.Error(w.Code) t.Fatal(http.StatusFound) } else if uri, err := url.Parse(w.HeaderMap.Get("Location")); err != nil { t.Fatal(err) } else if q := uri.Query(); len(q) == 0 { t.Fatal("no query") } else if respType, respType2 := strsetutil.New("code", "id_token"), request.FormValueSet(q.Get("response_type")); !reflect.DeepEqual(respType2, respType) { t.Error(respType2) t.Fatal(respType) } }
func parseTokenResponse(resp *http.Response) (*tokenResponse, error) { var buff struct { Access_token string Expires_in int Scope string Id_token string } if err := json.NewDecoder(resp.Body).Decode(&buff); err != nil { return nil, erro.Wrap(err) } else if buff.Access_token == "" { return nil, erro.New("no access token") } var exp time.Time if buff.Expires_in != 0 { exp = time.Now().Add(time.Duration(buff.Expires_in) * time.Second) } var scop map[string]bool if buff.Scope != "" { scop = request.FormValueSet(buff.Scope) } var idTok []byte if buff.Id_token != "" { idTok = []byte(buff.Id_token) } return &tokenResponse{ tok: buff.Access_token, exp: exp, scop: scop, idTok: idTok, }, nil }
func parseCoopResponse(resp *http.Response) (*coopResponse, error) { if resp.StatusCode != http.StatusOK { return nil, erro.New("invalid state ", resp.StatusCode) } else if contType := resp.Header.Get(tagContent_type); contType != contTypeJson { return nil, erro.New("invalid content type " + contType) } var buff struct { Tok string `json:"access_token"` TokType string `json:"token_type"` ExpIn int `json:"expires_in"` Scop string `json:"scope"` IdsTok string `json:"ids_token"` } if err := json.NewDecoder(resp.Body).Decode(&buff); err != nil { return nil, erro.Wrap(err) } else if buff.IdsTok == "" { return nil, erro.New("cannot get IDs token") } return &coopResponse{ tok: buff.Tok, tokType: buff.TokType, expIn: time.Duration(buff.ExpIn) * time.Second, scop: requtil.FormValueSet(buff.Scop), idsTok: []byte(buff.IdsTok), }, nil }
func testServer(t *testing.T, param *parameters, idpServ *testIdProvider) { idp := idpServ.info() errCh := make(chan error, 1) go func() { errCh <- serve(param) }() defer func() { param.shutCh <- struct{}{} }() selfUri, err := url.Parse("http://localhost:" + strconv.Itoa(param.socPort)) if err != nil { t.Fatal(err) } else if err := waitServer(selfUri.String()+param.pathOk, errCh, time.Now().Add(time.Minute)); err != nil { t.Fatal(err) } cookJar, err := cookiejar.New(nil) if err != nil { t.Fatal(err) } noRedi := func(req *http.Request, via []*http.Request) error { return errors.New("redirect flag") } // 認証前リクエスト。 reqPath := strings.TrimLeft(param.pathAuth, "/") + "/a/b/c" authReq, err := newAuthRequest(selfUri.String()+reqPath, idp.AuthUri()) if err != nil { t.Fatal(err) } authResp, err := (&http.Client{Jar: cookJar, CheckRedirect: noRedi}).Do(authReq) if err != nil { if e, ok := err.(*url.Error); !ok || e.Err.Error() != "redirect flag" { t.Fatal(err) } } defer authResp.Body.Close() server.LogResponse(level.DEBUG, authResp, false) if authResp.StatusCode != http.StatusFound { t.Error(authResp.StatusCode) t.Fatal(http.StatusFound) } authUri, err := url.Parse(authResp.Header.Get("Location")) if err != nil { t.Fatal(err) } else if authUri := authUri.Scheme + "://" + authUri.Host + authUri.Path; authUri != idp.AuthUri() { t.Error(authUri) t.Fatal(idp.AuthUri()) } sessExist := false for _, cook := range cookJar.Cookies(selfUri) { if cook.Name == param.asessLabel { sessExist = true break } } if !sessExist { t.Fatal("no new session") } authQ := authUri.Query() if len(authQ) == 0 { t.Fatal("no query") } else if respType := request.FormValueSet(authQ.Get("response_type")); len(respType) != 1 || !respType["code"] { t.Error(respType) t.Fatal("code") } else if scop := request.FormValueSet(authQ.Get("scope")); len(scop) != 1 || !scop["openid"] { t.Error(scop) t.Fatal("openid") } else if taId := authQ.Get("client_id"); taId != param.selfId { t.Error(taId) t.Fatal(param.selfId) } else if rediUri := authQ.Get("redirect_uri"); rediUri != param.rediUri { t.Error(rediUri) t.Fatal(param.rediUri) } else if authQ.Get("state") == "" { t.Fatal("no state") } else if authQ.Get("nonce") == "" { t.Fatal("no nonce") } // 認証後リクエスト。 { s, h, b, err := newTestTokenResponse(param.selfId, authQ.Get("nonce"), idp) if err != nil { t.Fatal(err) } idpServ.addResponse(s, h, b) } { s, h, b, err := newTestAccountResponse(idp) if err != nil { t.Fatal(err) } idpServ.addResponse(s, h, b) } cbReq, err := newCallbackRequest(selfUri.String()+param.pathCb, authQ.Get("state")) if err != nil { t.Fatal(err) } cbResp, err := (&http.Client{Jar: cookJar, CheckRedirect: noRedi}).Do(cbReq) if err != nil { if e, ok := err.(*url.Error); !ok || e.Err.Error() != "redirect flag" { t.Fatal(err) } } defer cbResp.Body.Close() server.LogResponse(level.DEBUG, cbResp, false) if cbResp.StatusCode != http.StatusFound { t.Error(cbResp.StatusCode) t.Fatal(http.StatusFound) } else if uri, err := url.Parse(cbResp.Header.Get("Location")); err != nil { t.Fatal(err) } else if uri.Path != reqPath { t.Error(uri.Path) t.Fatal(reqPath) } var buff struct { Iss string Sub string At_tag string At_exp int Email string } if jt, err := jwt.Parse([]byte(cbResp.Header.Get("X-Auth-User"))); err != nil { t.Fatal(err) } else if err := json.Unmarshal(jt.RawBody(), &buff); err != nil { t.Fatal(err) } else if buff.Iss != idp.Id() { t.Error(buff.Iss) t.Fatal(idp.Id()) } else if buff.Sub != test_acntId { t.Error(buff.Sub) t.Fatal(test_acntId) } else if len(buff.At_tag) != param.tokTagLen { t.Error(len(buff.At_tag), buff.At_tag) t.Fatal(param.tokTagLen) } else if buff.At_exp == 0 { t.Fatal("no at_exp") } else if buff.Email != test_acntEmail { t.Error(buff.Email) t.Fatal(test_acntEmail) } }
func (this *environment) authServe(w http.ResponseWriter, r *http.Request) error { req, err := parseAuthRequest(r) if err != nil { return erro.Wrap(server.NewError(http.StatusBadRequest, erro.Unwrap(err).Error(), err)) } log.Debug(this.logPref, "Parsed authentication request") authUri := req.authUri() queries := authUri.Query() // response_type var idp string respType := map[string]bool{tagCode: true} rawAuthUri := authUri.Scheme + "://" + authUri.Host + authUri.Path if idps, err := this.idpDb.Search(map[string]string{ tagAuthorization_endpoint: "^" + rawAuthUri + "$", }); err != nil { return erro.Wrap(err) } else if len(idps) == 1 { idp = idps[0].Id() log.Debug(this.logPref, "Destination is in ID provider "+idp) } else { // ID プロバイダ選択サービスか何か。 respType[tagId_token] = true log.Debug(this.logPref, "Destination "+rawAuthUri+" is not ID provider") } queries.Set(tagResponse_type, request.ValueSetForm(respType)) // scope if scop := request.FormValueSet(queries.Get(tagScope)); !scop[tagOpenid] { scop[tagOpenid] = true queries.Set(tagScope, request.ValueSetForm(scop)) log.Debug(this.logPref, `Added scope "`+tagOpenid+`"`) } // client_id ta := queries.Get(tagClient_id) if ta == "" { ta = this.selfId queries.Set(tagClient_id, ta) log.Debug(this.logPref, "Act as default TA "+ta) } else { log.Debug(this.logPref, "Act as TA "+ta) } // redirect_uri rediUri := queries.Get(tagRedirect_uri) if rediUri == "" { rediUri = this.rediUri queries.Set(tagRedirect_uri, rediUri) log.Debug(this.logPref, "Use default redirect uri "+rediUri) } else { log.Debug(this.logPref, "Use redirect uri "+rediUri) } // state stat := this.idGen.String(this.statLen) queries.Set(tagState, stat) log.Debug(this.logPref, "Use state "+logutil.Mosaic(stat)) // nonce nonc := this.idGen.String(this.noncLen) queries.Set(tagNonce, nonc) log.Debug(this.logPref, "Use nonce "+logutil.Mosaic(nonc)) authUri.RawQuery = queries.Encode() sess := asession.New( this.idGen.String(this.sessLen), time.Now().Add(this.sessExpIn), req.path(), idp, ta, rediUri, stat, nonc, ) if err := this.sessDb.Save(sess, sess.Expires().Add(this.sessDbExpIn-this.sessExpIn)); err != nil { return erro.Wrap(err) } log.Info(this.logPref, "Generated user session "+logutil.Mosaic(sess.Id())) http.SetCookie(w, this.newCookie(sess.Id(), sess.Expires())) w.Header().Add(tagCache_control, tagNo_store) w.Header().Add(tagPragma, tagNo_cache) uri := authUri.String() http.Redirect(w, r, uri, http.StatusFound) log.Info(this.logPref, "Redirect to "+uri) return nil }