func newTestRequestWithParams(hndl *handler, idp, subIdp idpdb.Element, params, subParams map[string]interface{}) (*http.Request, error) { r, err := http.NewRequest("GET", "http://localhost/coop", nil) if err != nil { return nil, erro.Wrap(err) } codTok := jwt.New() codTok.SetHeader("alg", test_idpSigAlg) codTok.SetClaim("iss", idp.Id()) codTok.SetClaim("sub", test_cod) codTok.SetClaim("aud", audience.New(hndl.selfId)) codTok.SetClaim("from_client", test_frTa.Id()) codTok.SetClaim("user_tag", test_acntTag) codTok.SetClaim("user_tags", []string{test_subAcnt1Tag}) codTok.SetClaim("ref_hash", test_refHash) for k, v := range params { codTok.SetClaim(k, v) } if err := codTok.Sign(idp.Keys()); err != nil { return nil, erro.Wrap(err) } data, err := codTok.Encode() if err != nil { return nil, erro.Wrap(err) } r.Header.Set("X-Edo-Code-Tokens", string(data)) subCodTok := jwt.New() subCodTok.SetHeader("alg", test_subIdpSigAlg) subCodTok.SetClaim("iss", subIdp.Id()) subCodTok.SetClaim("sub", test_subCod) subCodTok.SetClaim("aud", audience.New(hndl.selfId)) subCodTok.SetClaim("user_tags", []string{test_subAcnt2Tag}) subCodTok.SetClaim("ref_hash", test_refHash) for k, v := range subParams { subCodTok.SetClaim(k, v) } if err := subCodTok.Sign(subIdp.Keys()); err != nil { return nil, erro.Wrap(err) } subData, err := subCodTok.Encode() if err != nil { return nil, erro.Wrap(err) } r.Header.Add("X-Edo-Code-Tokens", string(subData)) return r, nil }
func newTestMainIdpResponseWithParams(hndl *handler, idp idpdb.Element, params, idsTokParams map[string]interface{}) (status int, header http.Header, body []byte, err error) { now := time.Now() idsTok := jwt.New() idsTok.SetHeader("alg", test_idpSigAlg) idsTok.SetClaim("iss", idp.Id()) idsTok.SetClaim("sub", test_frTa.Id()) idsTok.SetClaim("aud", audience.New(hndl.selfId)) idsTok.SetClaim("exp", now.Add(time.Minute).Unix()) idsTok.SetClaim("iat", now.Unix()) idsTok.SetClaim("ids", map[string]map[string]interface{}{ test_acntTag: { "sub": test_acntId, "email": test_acntEmail, }, test_subAcnt1Tag: { "sub": test_subAcnt1Id, "email": test_subAcnt1Email, }, }) for k, v := range idsTokParams { idsTok.SetClaim(k, v) } if err := idsTok.Sign(idp.Keys()); err != nil { return 0, nil, nil, erro.Wrap(err) } data, err := idsTok.Encode() if err != nil { return 0, nil, nil, erro.Wrap(err) } m := map[string]interface{}{ "access_token": test_tok, "token_type": "Bearer", "expires_in": 1234, "scope": "openid email", "ids_token": string(data), } for k, v := range params { if v == nil { delete(m, k) } else { m[k] = v } } body, err = json.Marshal(m) if err != nil { return 0, nil, nil, erro.Wrap(err) } return http.StatusOK, http.Header{"Content-Type": {"application/json"}}, body, nil }
func newTestTokenResponse(page *Page, idp idpdb.Element, clms map[string]interface{}) (status int, hader http.Header, body []byte, err error) { m := map[string]interface{}{ "access_token": test_tok, "token_type": "Bearer", "expires_in": 3600, "scope": "openid email", } idTok := jwt.New() idTok.SetHeader("alg", test_idpSigAlg) idTok.SetClaim("iss", idp.Id()) idTok.SetClaim("sub", test_acntId) idTok.SetClaim("aud", page.selfId) now := time.Now() idTok.SetClaim("exp", now.Add(time.Minute).Unix()) idTok.SetClaim("iat", now.Unix()) idTok.SetClaim("nonce", test_nonc) hGen := jwt.HashGenerator(test_idpSigAlg) if !hGen.Available() { return 0, nil, nil, erro.New("unsupported algorithm " + test_idpSigAlg) } idTok.SetClaim("at_hash", hashutil.Hashing(hGen.New(), []byte(test_tok))) for k, v := range clms { idTok.SetClaim(k, v) } if err := idTok.Sign(idp.Keys()); err != nil { return 0, nil, nil, erro.Wrap(err) } data, err := idTok.Encode() if err != nil { return 0, nil, nil, erro.Wrap(err) } m["id_token"] = string(data) body, err = json.Marshal(m) if err != nil { return 0, nil, nil, erro.Wrap(err) } return http.StatusOK, nil, body, nil }
func newCallbackRequestWithIdToken(page *Page, idp idpdb.Element, clms map[string]interface{}) (*http.Request, error) { q := url.Values{} q.Set("code", test_cod) q.Set("state", test_stat) idTok := jwt.New() idTok.SetHeader("alg", test_idpSigAlg) idTok.SetClaim("iss", idp.Id()) idTok.SetClaim("sub", test_acntId) idTok.SetClaim("aud", page.selfId) now := time.Now() idTok.SetClaim("exp", now.Add(time.Minute).Unix()) idTok.SetClaim("iat", now.Unix()) idTok.SetClaim("nonce", test_nonc) hGen := jwt.HashGenerator(test_idpSigAlg) if !hGen.Available() { return nil, erro.New("unsupported algorithm " + test_idpSigAlg) } idTok.SetClaim("c_hash", hashutil.Hashing(hGen.New(), []byte(test_cod))) for k, v := range clms { idTok.SetClaim(k, v) } if err := idTok.Sign(idp.Keys()); err != nil { return nil, erro.Wrap(err) } data, err := idTok.Encode() if err != nil { return nil, erro.Wrap(err) } q.Set("id_token", string(data)) r, err := http.NewRequest("GET", "http://localhost/callback?"+q.Encode(), nil) if err != nil { return nil, erro.Wrap(err) } r.AddCookie(&http.Cookie{ Name: page.sessLabel, Value: test_sessId, }) return r, nil }
// TA 認証用署名をつくる。 func makeAssertion(hndl *handler, keys []jwk.Key, aud string) ([]byte, error) { ass := jwt.New() ass.SetHeader(tagAlg, hndl.sigAlg) if hndl.sigKid != "" { ass.SetHeader(tagKid, hndl.sigKid) } ass.SetClaim(tagIss, hndl.selfId) ass.SetClaim(tagSub, hndl.selfId) ass.SetClaim(tagAud, aud) ass.SetClaim(tagJti, hndl.idGen.String(hndl.jtiLen)) now := time.Now() ass.SetClaim(tagExp, now.Add(hndl.jtiExpIn).Unix()) ass.SetClaim(tagIat, now.Unix()) if err := ass.Sign(keys); err != nil { return nil, erro.Wrap(err) } data, err := ass.Encode() if err != nil { return nil, erro.Wrap(err) } return data, nil }
func (this *environment) callbackServe(w http.ResponseWriter, r *http.Request) error { if this.sessId == "" { return erro.Wrap(server.NewError(http.StatusBadRequest, "no session ", nil)) } sess, err := this.sessDb.Get(this.sessId) if err != nil { return erro.Wrap(err) } else if sess == nil { return erro.Wrap(server.NewError(http.StatusBadRequest, "declared user session is not exist", nil)) } this.sess = sess log.Debug(this.logPref, "Declared user session is exist") savedDate := sess.Date() sess.Invalidate() if ok, err := this.sessDb.Replace(sess, savedDate); err != nil { return erro.Wrap(err) } else if !ok { return erro.Wrap(server.NewError(http.StatusBadRequest, "reused user session", nil)) } req, err := parseCallbackRequest(r) if err != nil { return erro.Wrap(server.NewError(http.StatusBadRequest, erro.Unwrap(err).Error(), err)) } log.Debug(this.logPref, "Parsed callback request") if req.state() != sess.State() { return erro.Wrap(server.NewError(http.StatusForbidden, "invalid state", nil)) } var idp idpdb.Element var attrs1 map[string]interface{} if sess.IdProvider() != "" { idp, err = this.idpDb.Get(sess.IdProvider()) if err != nil { return erro.Wrap(err) } else if idp == nil { return erro.Wrap(server.NewError(http.StatusBadRequest, "ID provider "+sess.IdProvider()+" is not exist", nil)) } log.Debug(this.logPref, "ID provider "+idp.Id()+" is exist") } else { idTok, err := parseIdToken(req.idToken()) if err != nil { return erro.Wrap(server.NewError(http.StatusBadRequest, erro.Unwrap(err).Error(), err)) } idp, err = this.idpDb.Get(idTok.idProvider()) if err != nil { return erro.Wrap(err) } else if idp == nil { return erro.Wrap(server.NewError(http.StatusBadRequest, "ID provider "+idTok.idProvider()+" is not exist", nil)) } log.Debug(this.logPref, "ID provider "+idp.Id()+" is exist") if idTok.nonce() != sess.Nonce() { return erro.Wrap(server.NewError(http.StatusForbidden, "invalid nonce", nil)) } else if err := idTok.verify(idp.Keys()); err != nil { return erro.Wrap(server.NewError(http.StatusForbidden, erro.Unwrap(err).Error(), err)) } else if err := idTok.verifyCodeHash(req.code()); err != nil { return erro.Wrap(server.NewError(http.StatusForbidden, erro.Unwrap(err).Error(), err)) } attrs1 = idTok.attributes() log.Debug(this.logPref, "ID token is OK") } // アクセストークンを取得する。 tok, idTok, err := this.getAccessToken(req, idp) if err != nil { return erro.Wrap(err) } // アカウント情報を取得する。 attrs2, err := this.getAccountInfo(req, tok, idp) if err != nil { return erro.Wrap(err) } // アカウント情報をまとめる。 jt := jwt.New() jt.SetHeader(tagAlg, tagNone) for _, m := range []map[string]interface{}{attrs1, idTok.attributes(), attrs2} { for k, v := range m { jt.SetClaim(k, v) } } jt.SetClaim(tagAt_tag, tok.Tag()) jt.SetClaim(tagAt_exp, tok.Expires().Unix()) buff, err := jt.Encode() if err != nil { return erro.Wrap(err) } // フロントエンドのためにセッション期限を延長する。 now := time.Now() http.SetCookie(w, this.newCookie(sess.Id(), now.Add(-time.Second))) http.SetCookie(w, this.newFrontCookie(this.idGen.String(this.fsessLen), now.Add(this.fsessExpIn))) log.Info(this.logPref, "Upgrade user session to frontend session") // フロントエンドが使うので保存しなくて良い。 w.Header().Add(tagX_auth_user, string(buff)) w.Header().Add(tagCache_control, tagNo_store) w.Header().Add(tagPragma, tagNo_cache) http.Redirect(w, r, sess.Path(), http.StatusFound) log.Info(this.logPref, "Redirect to "+sess.Path()) return nil }
// 認可コードを使って、ID プロバイダからアクセストークンを取得する。 func (this *environment) getAccessToken(req *callbackRequest, idp idpdb.Element) (*token.Element, *idToken, error) { keys, err := this.keyDb.Get() if err != nil { return nil, nil, erro.Wrap(err) } queries := url.Values{} // grant_type queries.Set(tagGrant_type, tagAuthorization_code) // code queries.Set(tagCode, req.code()) // redirect_uri queries.Set(tagRedirect_uri, this.sess.RedirectUri()) // client_id queries.Set(tagClient_id, this.sess.Ta()) // client_assertion_type queries.Set(tagClient_assertion_type, cliAssTypeJwt_bearer) // client_assertion ass := jwt.New() now := time.Now() ass.SetHeader(tagAlg, this.sigAlg) ass.SetClaim(tagIss, this.sess.Ta()) ass.SetClaim(tagSub, this.sess.Ta()) ass.SetClaim(tagAud, idp.TokenUri()) ass.SetClaim(tagJti, this.idGen.String(this.jtiLen)) ass.SetClaim(tagExp, now.Add(this.jtiExpIn).Unix()) ass.SetClaim(tagIat, now.Unix()) if err := ass.Sign(keys); err != nil { return nil, nil, erro.Wrap(err) } buff, err := ass.Encode() if err != nil { return nil, nil, erro.Wrap(err) } queries.Set(tagClient_assertion, string(buff)) tokReq, err := http.NewRequest("POST", idp.TokenUri(), strings.NewReader(queries.Encode())) if err != nil { return nil, nil, erro.Wrap(err) } tokReq.Header.Set(tagContent_type, contTypeForm) server.LogRequest(level.DEBUG, tokReq, this.debug, this.logPref) resp, err := http.DefaultClient.Do(tokReq) if err != nil { return nil, nil, erro.Wrap(err) } defer resp.Body.Close() server.LogResponse(level.DEBUG, resp, this.debug, this.logPref) log.Info(this.logPref, "Got token response from "+idp.Id()) tokResp, err := parseTokenResponse(resp) if err != nil { return nil, nil, erro.Wrap(server.NewError(http.StatusForbidden, "cannot get access token", nil)) } tok := token.New(tokResp.token(), this.idGen.String(this.tokTagLen), tokResp.expires(), idp.Id(), tokResp.scope()) log.Info(this.logPref, "Got access token "+logutil.Mosaic(tok.Id())) if err := this.tokDb.Save(tok, time.Now().Add(this.tokDbExpIn)); err != nil { return nil, nil, erro.Wrap(err) } log.Info(this.logPref, "Saved access token "+logutil.Mosaic(tok.Id())) idTok, err := parseIdToken(tokResp.idToken()) if err != nil { return nil, nil, erro.Wrap(server.NewError(http.StatusForbidden, erro.Unwrap(err).Error(), err)) } else if idTok.nonce() != this.sess.Nonce() { return nil, nil, erro.Wrap(server.NewError(http.StatusForbidden, "invalid nonce", nil)) } else if err := idTok.verify(idp.Keys()); err != nil { return nil, nil, erro.Wrap(server.NewError(http.StatusForbidden, erro.Unwrap(err).Error(), err)) } else if idTok.tokenHash() != nil { if err := idTok.verifyTokenHash(tok.Id()); err != nil { return nil, nil, erro.Wrap(server.NewError(http.StatusForbidden, erro.Unwrap(err).Error(), err)) } } log.Info(this.logPref, "ID token is OK") return tok, idTok, nil }
func (this *environment) serve(w http.ResponseWriter, r *http.Request) error { req, err := parseRequest(r) if err != nil { return erro.Wrap(idperr.New(idperr.Invalid_request, erro.Unwrap(err).Error(), http.StatusBadRequest, err)) } var acntTag string tags := map[string]bool{} var refHash string type idpUnit struct { idp idpdb.Element codTok *codeToken } units := []*idpUnit{} for _, rawCodTok := range req.codeTokens() { codTok, err := parseCodeToken(rawCodTok) if err != nil { return erro.Wrap(idperr.New(idperr.Invalid_request, erro.Unwrap(err).Error(), http.StatusBadRequest, err)) } else if !codTok.audience()[this.selfId] { return erro.Wrap(idperr.New(idperr.Invalid_request, "invalid audience", http.StatusBadRequest, nil)) } else if codTok.referralHash() == "" && len(req.codeTokens()) > 1 { return erro.Wrap(idperr.New(idperr.Invalid_request, "no referral hash", http.StatusBadRequest, nil)) } else if codTok.accountTag() != "" { if acntTag != "" { return erro.Wrap(idperr.New(idperr.Invalid_request, "two main token", http.StatusBadRequest, nil)) } acntTag = codTok.accountTag() log.Debug(this.logPref, "Main account tag is "+acntTag) if codTok.fromTa() == "" { return erro.Wrap(idperr.New(idperr.Invalid_request, "no from-TA in main token", http.StatusBadRequest, nil)) } log.Debug(this.logPref, "From-TA is "+codTok.fromTa()) } else if len(codTok.accountTags()) == 0 { return erro.Wrap(idperr.New(idperr.Invalid_request, "no account tags in sub token", http.StatusBadRequest, nil)) } for tag := range codTok.accountTags() { if tags[tag] { return erro.Wrap(idperr.New(idperr.Invalid_request, "tag "+tag+" overlaps", http.StatusBadRequest, nil)) } tags[tag] = true log.Debug(this.logPref, "Account tag is "+tag) } if codTok.referralHash() != "" { if refHash == "" { refHash = codTok.referralHash() } else if codTok.referralHash() != refHash { return erro.Wrap(idperr.New(idperr.Invalid_request, "invalid referral hash", http.StatusBadRequest, nil)) } } var idp idpdb.Element if idp, err = this.idpDb.Get(codTok.idProvider()); err != nil { return erro.Wrap(err) } else if idp == nil { return erro.Wrap(idperr.New(idperr.Invalid_request, "ID provider "+codTok.idProvider()+" is not exist", http.StatusBadRequest, nil)) } log.Debug(this.logPref, "ID provider "+idp.Id()+" is exist") if err := codTok.verify(idp.Keys()); err != nil { return erro.Wrap(idperr.New(idperr.Invalid_request, erro.Unwrap(err).Error(), http.StatusBadRequest, err)) } log.Debug(this.logPref, "Verified cooperation code") units = append(units, &idpUnit{idp, codTok}) } if acntTag == "" { return erro.Wrap(idperr.New(idperr.Invalid_request, "no main account tag", http.StatusBadRequest, nil)) } log.Debug(this.logPref, "Cooperation codes are OK") var tok *token.Element var mainAttrs map[string]interface{} tagToAttrs := map[string]map[string]interface{}{} var frTa string for _, unit := range units { var tToA map[string]map[string]interface{} var fT string if unit.codTok.accountTag() != "" { fT, tok, tToA, err = this.getInfoFromMainIdProvider(unit.idp, unit.codTok) if err != nil { return erro.Wrap(err) } log.Debug(this.logPref, "Got account info from main ID provider "+unit.idp.Id()) } else { fT, tToA, err = this.getInfoFromSubIdProvider(unit.idp, unit.codTok) if err != nil { return erro.Wrap(err) } log.Debug(this.logPref, "Got account info from sub ID provider "+unit.idp.Id()) } for tag, attrs := range tToA { if tag == acntTag { attrs[tagIss] = unit.idp.Id() attrs[tagAt_tag] = tok.Tag() attrs[tagAt_exp] = tok.Expires().Unix() mainAttrs = attrs } else { attrs[tagIss] = unit.idp.Id() tagToAttrs[tag] = attrs } } if frTa == "" { frTa = fT } else if frTa != fT { return erro.Wrap(idperr.New(idperr.Invalid_request, "two from-TA ID", http.StatusBadRequest, nil)) } } log.Debug(this.logPref, "Got all account info") jt := jwt.New() jt.SetHeader(tagAlg, tagNone) for k, v := range mainAttrs { jt.SetClaim(k, v) } mainInfo, err := jt.Encode() if err != nil { return erro.Wrap(err) } var relInfo []byte sessFlag := true if len(tagToAttrs) > 0 { jt = jwt.New() jt.SetHeader(tagAlg, tagNone) for acntTag, attrs := range tagToAttrs { jt.SetClaim(acntTag, attrs) if sessFlag { for k := range attrs { if !sessionEnable(k) { sessFlag = false } } } } relInfo, err = jt.Encode() if err != nil { return erro.Wrap(err) } } w.Header().Set(tagX_auth_user, string(mainInfo)) w.Header().Set(tagX_auth_user_tag, acntTag) w.Header().Set(tagX_auth_from_id, frTa) if relInfo != nil { w.Header().Set(tagX_auth_users, string(relInfo)) } if sessFlag { sessId := this.idGen.String(this.sessLen) http.SetCookie(w, this.handler.newCookie(sessId, tok.Expires())) log.Debug(this.logPref, "Report session "+logutil.Mosaic(sessId)) } return nil }