func parseCodeToken(raw []byte) (*codeToken, error) { base, err := jwt.Parse(raw) if err != nil { return nil, erro.Wrap(err) } var buff struct { Idp string `json:"iss"` Cod string `json:"sub"` Aud audience.Audience `json:"aud"` FrTa string `json:"from_client"` AcntTag string `json:"user_tag"` AcntTags strset.Set `json:"user_tags"` RefHash string `json:"ref_hash"` } if err := json.Unmarshal(base.RawBody(), &buff); err != nil { return nil, erro.Wrap(err) } else if buff.Idp == "" { return nil, erro.New("no ID provider ID") } else if buff.Cod == "" { return nil, erro.New("no code") } else if len(buff.Aud) == 0 { return nil, erro.New("no audience") } return &codeToken{ base: base, idp: buff.Idp, cod: buff.Cod, aud: buff.Aud, frTa: buff.FrTa, acntTag: buff.AcntTag, acntTags: buff.AcntTags, refHash: buff.RefHash, }, nil }
func parseIdToken(raw []byte) (*idToken, error) { base, err := jwt.Parse(raw) if err != nil { return nil, erro.Wrap(err) } alg, _ := base.Header(tagAlg).(string) if alg == "" { return nil, erro.New("no alg") } var buff struct { Idp string `json:"iss"` Nonc string `json:"nonce"` CHash string `json:"c_hash"` AtHash string `json:"at_hash"` } if err := json.Unmarshal(base.RawBody(), &buff); err != nil { return nil, erro.Wrap(err) } else if buff.Idp == "" { return nil, erro.New("no ID provider ID") } else if buff.Nonc == "" { return nil, erro.New("no nonce") } var cHash, atHash []byte if buff.CHash != "" { cHash, err = base64.RawURLEncoding.DecodeString(buff.CHash) if err != nil { return nil, erro.Wrap(err) } } if buff.AtHash != "" { atHash, err = base64.RawURLEncoding.DecodeString(buff.AtHash) if err != nil { return nil, erro.Wrap(err) } } return &idToken{ base: base, alg: alg, idp: buff.Idp, nonc: buff.Nonc, cHash: cHash, atHash: atHash, }, nil }
func parseIdsToken(raw []byte) (*idsToken, error) { base, err := jwt.Parse(raw) if err != nil { return nil, erro.Wrap(err) } var buff struct { Idp string `json:"iss"` FrTa string `json:"sub"` Aud audience.Audience `json:"aud"` Exp int64 `json:"exp"` Date int64 `json:"iat"` TagToAttrs map[string]map[string]interface{} `json:"ids"` } if err := json.Unmarshal(base.RawBody(), &buff); err != nil { return nil, erro.Wrap(err) } else if buff.Idp == "" { return nil, erro.New("no ID provider ID") } else if buff.FrTa == "" { return nil, erro.New("no from-TA ID") } else if buff.Aud == nil { return nil, erro.New("no audience") } else if buff.Exp == 0 { return nil, erro.New("no expiration date") } else if buff.Date == 0 { return nil, erro.New("no published date") } else if len(buff.TagToAttrs) == 0 { return nil, erro.New("no IDs") } return &idsToken{ base: base, idp: buff.Idp, frTa: buff.FrTa, aud: buff.Aud, exp: time.Unix(buff.Exp, 0), date_: time.Unix(buff.Date, 0), tagToAttrs: buff.TagToAttrs, }, nil }
// 仲介コードが 2 つ以上の場合の正常系。 // レスポンスが X-Auth-User, X-Auth-User-Tag, X-Auth-Users, X-Auth-From-Id を含むことの検査。 // 主体情報が iss, sub, at_tag, at_exp クレームを含むことの検査。 // 主体でないアカウント情報が iss, sub クレームを含むことの検査。 func TestMultiNormal(t *testing.T) { // //////////////////////////////// // logutil.SetupConsole(logRoot, level.ALL) // defer logutil.SetupConsole(logRoot, level.OFF) // //////////////////////////////// idpServ, err := newTestIdProvider([]jwk.Key{test_idpKey}) if err != nil { t.Fatal(err) } defer idpServ.close() idp := idpServ.info() subIdpServ, err := newTestIdProvider([]jwk.Key{test_subIdpKey}) if err != nil { t.Fatal(err) } defer subIdpServ.close() subIdp := subIdpServ.info() hndl := newTestHandler([]jwk.Key{test_toTaKey}, []idpdb.Element{idp, subIdp}) r, err := newTestRequest(hndl, idp, subIdp) if err != nil { t.Fatal(err) } var reqCh <-chan *http.Request { s, h, b, err := newTestMainIdpResponse(hndl, idp) if err != nil { t.Fatal(err) } reqCh = idpServ.addResponse(s, h, b) } var subReqCh <-chan *http.Request { s, h, b, err := newTestSubIdpResponse(hndl, subIdp) if err != nil { t.Fatal(err) } subReqCh = subIdpServ.addResponse(s, h, b) } w := httptest.NewRecorder() hndl.ServeHTTP(w, r) select { case req := <-reqCh: if contType, contType2 := "application/json", req.Header.Get("Content-Type"); contType2 != contType { t.Error(contType) t.Fatal(contType2) } var buff struct { Grant_type string Code string Claims *claims.Request User_claims claims.Claims } if err := json.NewDecoder(req.Body).Decode(&buff); err != nil { t.Fatal(err) } else if grntType := "cooperation_code"; buff.Grant_type != grntType { t.Error(buff.Grant_type) t.Fatal(grntType) } else if buff.Code != test_cod { t.Error(buff.Code) t.Fatal(test_cod) } case <-time.After(time.Minute): t.Fatal("no request") } select { case req := <-subReqCh: if contType, contType2 := "application/json", req.Header.Get("Content-Type"); contType2 != contType { t.Error(contType) t.Fatal(contType2) } var buff struct { Grant_type string Code string Claims *claims.Request User_claims claims.Claims } if err := json.NewDecoder(req.Body).Decode(&buff); err != nil { t.Fatal(err) } else if grntType := "cooperation_code"; buff.Grant_type != grntType { t.Error(buff.Grant_type) t.Fatal(grntType) } else if buff.Code != test_subCod { t.Error(buff.Code) t.Fatal(test_subCod) } case <-time.After(time.Minute): t.Fatal("no request") } if w.Code != http.StatusOK { t.Error(w.Code) t.Fatal(http.StatusOK) } acntJt, err := jwt.Parse([]byte(w.HeaderMap.Get("X-Auth-User"))) if err != nil { t.Fatal(err) } var acntBuff struct { Iss string Sub string At_tag string At_exp int64 Email string } if err := json.Unmarshal(acntJt.RawBody(), &acntBuff); err != nil { t.Fatal(err) } else if acntBuff.Iss != idp.Id() { t.Error(acntBuff.Iss) t.Fatal(idp.Id()) } else if acntBuff.Sub != test_acntId { t.Error(acntBuff.Sub) t.Fatal(test_acntId) } else if acntBuff.At_tag == "" { t.Fatal("no token tag") } else if now, exp := time.Now(), time.Unix(acntBuff.At_exp, 0); exp.Before(now) { t.Error("expired") t.Error(now) t.Fatal(exp) } else if acntBuff.Email != test_acntEmail { t.Error(acntBuff.Email) t.Fatal(test_acntEmail) } acntTag := w.HeaderMap.Get("X-Auth-User-Tag") if acntTag != test_acntTag { t.Error(acntTag) t.Fatal(test_acntTag) } acntsJt, err := jwt.Parse([]byte(w.HeaderMap.Get("X-Auth-Users"))) if err != nil { t.Fatal(err) } type account struct { Iss string Sub string Email string } var acntsBuff map[string]*account if err := json.Unmarshal(acntsJt.RawBody(), &acntsBuff); err != nil { t.Fatal(err) } else if len(acntsBuff) == 0 { t.Fatal("no accounts") } else if subAcnt1 := acntsBuff[test_subAcnt1Tag]; subAcnt1 == nil { t.Fatal("no sub account") } else if subAcnt1.Iss != idp.Id() { t.Error(subAcnt1.Iss) t.Fatal(idp.Id()) } else if subAcnt1.Sub != test_subAcnt1Id { t.Error(subAcnt1.Sub) t.Fatal(test_subAcnt1Id) } else if subAcnt1.Email != test_subAcnt1Email { t.Error(subAcnt1.Email) t.Fatal(test_subAcnt1Email) } else if subAcnt2 := acntsBuff[test_subAcnt2Tag]; subAcnt2 == nil { t.Fatal("no sub account") } else if subAcnt2.Iss != subIdp.Id() { t.Error(subAcnt2.Iss) t.Fatal(subIdp.Id()) } else if subAcnt2.Sub != test_subAcnt2Id { t.Error(subAcnt2.Sub) t.Fatal(test_subAcnt2Id) } else if subAcnt2.Email != test_subAcnt2Email { t.Error(subAcnt2.Email) t.Fatal(test_subAcnt2Email) } }
// 正常系。 // 元のリクエストパスにリダイレクトさせることの検査。 // X-Auth-User ヘッダに iss, sub, at_tag, at_exp の入った JWT を入れることの検査。 // X-Auth-User ヘッダに追加属性を入れることの検査。 func TestCallback(t *testing.T) { // //////////////////////////////// // logutil.SetupConsole(logRoot, level.ALL) // defer logutil.SetupConsole(logRoot, level.OFF) // //////////////////////////////// idpServ, err := newTestIdProvider([]jwk.Key{test_idpKey}) if err != nil { t.Fatal(err) } defer idpServ.close() idp := idpServ.info() page := newTestPage([]jwk.Key{test_taKey}, []idpdb.Element{idp}) now := time.Now() sess := asession.New(test_sessId, now.Add(page.sessExpIn), test_reqPath, idp.Id(), page.selfId, page.rediUri, test_stat, test_nonc) page.sessDb.Save(sess, now.Add(time.Minute)) r, err := newCallbackRequest(page) if err != nil { t.Fatal(err) } s1, h1, b1, err := newTestTokenResponse(page, idp, nil) if err != nil { t.Fatal(err) } req1Ch := idpServ.addResponse(s1, h1, b1) s2, h2, b2, err := newTestAccountResponse(page, idp) if err != nil { t.Fatal(err) } req2Ch := idpServ.addResponse(s2, h2, b2) w := httptest.NewRecorder() page.HandleCallback(w, r) select { case req := <-req1Ch: if contType, contType2 := "application/x-www-form-urlencoded", req.Header.Get("Content-Type"); contType2 != contType { t.Error(contType) t.Fatal(contType2) } else if grntType, grntType2 := "authorization_code", req.FormValue("grant_type"); grntType2 != grntType { t.Error(grntType) t.Fatal(grntType2) } else if cod := req.FormValue("code"); cod != test_cod { t.Error(cod) t.Fatal(test_cod) } else if rediUri := req.FormValue("redirect_uri"); rediUri != page.rediUri { t.Error(rediUri) t.Fatal(page.rediUri) } else if taId := req.FormValue("client_id"); taId != page.selfId { t.Error(taId) t.Fatal(page.selfId) } else if assType, assType2 := "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", req.FormValue("client_assertion_type"); assType2 != assType { t.Error(assType2) t.Fatal(assType) } ass, err := jwt.Parse([]byte(req.FormValue("client_assertion"))) if err != nil { t.Fatal(err) } else if !ass.IsSigned() { t.Fatal("not signed") } else if err := ass.Verify([]jwk.Key{test_taKey}); err != nil { t.Fatal(err) } var buff struct { Iss string Sub string Aud audience.Audience Jti string Exp int Iat int } if err := json.Unmarshal(ass.RawBody(), &buff); err != nil { t.Fatal(err) } else if buff.Iss != page.selfId { t.Error(buff.Iss) t.Fatal(page.selfId) } else if buff.Sub != page.selfId { t.Error(buff.Sub) t.Fatal(page.selfId) } else if !buff.Aud[idp.TokenUri()] { t.Error(buff.Aud) t.Fatal(idp.TokenUri()) } else if len(buff.Jti) != page.jtiLen { t.Error(len(buff.Jti), " "+buff.Jti) t.Fatal(page.jtiLen) } else if buff.Exp == 0 { t.Fatal("no exp") } else if buff.Iat == 0 { t.Fatal("no iat") } else if !(buff.Iat < buff.Exp) { t.Error("exp not after iat") t.Error(buff.Iat) t.Fatal(buff.Exp) } case <-time.After(time.Minute): t.Fatal("no request") } select { case req := <-req2Ch: if auth := strings.Fields(req.Header.Get("Authorization")); len(auth) != 2 { t.Error("not 2 fields") t.Fatal(auth) } else if auth[0] != "Bearer" { t.Error(auth[0]) t.Fatal("Bearer") } else if auth[1] != test_tok { t.Error(auth[1]) t.Fatal(test_tok) } case <-time.After(time.Minute): t.Fatal("no request") } if w.Code != http.StatusFound { t.Error(w.Code) t.Fatal(http.StatusFound) } else if uri, err := url.Parse(test_reqPath); err != nil { t.Fatal(err) } else if uri2, err := url.Parse(w.HeaderMap.Get("Location")); err != nil { t.Fatal(err) } else if !reflect.DeepEqual(uri2, uri) { t.Error(uri2) t.Fatal(uri) } var buff struct { Iss string Sub string At_tag string At_exp int Email string } if jt, err := jwt.Parse([]byte(w.HeaderMap.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) != page.tokTagLen { t.Error(len(buff.At_tag), buff.At_tag) t.Fatal(page.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 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) } }