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 }
func serve(param *parameters) (err error) { // バックエンドの準備。 stopper := server.NewStopper() redPools := driver.NewRedisPoolSet(param.redTimeout, param.redPoolSize, param.redPoolExpIn) defer redPools.Close() monPools := driver.NewMongoPoolSet(param.monTimeout) defer monPools.Close() // 鍵。 var keyDb keydb.Db switch param.keyDbType { case "file": keyDb = keydb.NewFileDb(param.keyDbPath) log.Info("Use keys in directory " + param.keyDbPath) case "redis": keyDb = keydb.NewRedisCache(keydb.NewFileDb(param.keyDbPath), redPools.Get(param.keyDbAddr), param.keyDbTag+"."+param.selfId, param.keyDbExpIn) log.Info("Use keys in directory " + param.keyDbPath + " with redis " + param.keyDbAddr + ": " + param.keyDbTag + "." + param.selfId) default: return erro.New("invalid key DB type " + param.keyDbType) } // web データ。 var webDb webdb.Db switch param.webDbType { case "direct": webDb = webdb.NewDirectDb() log.Info("Get web data directly") case "redis": webDb = webdb.NewRedisCache(webdb.NewDirectDb(), redPools.Get(param.webDbAddr), param.webDbTag, param.webDbExpIn) log.Info("Get web data with redis " + param.webDbAddr + ": " + param.webDbTag) default: return erro.New("invalid web data DB type " + param.webDbType) } // ID プロバイダ情報。 var idpDb idpdb.Db switch param.idpDbType { case "mongo": pool, err := monPools.Get(param.idpDbAddr) if err != nil { return erro.Wrap(err) } idpDb = idpdb.NewMongoDb(pool, param.idpDbTag, param.idpDbTag2, webDb) log.Info("Use IdP info in mongodb " + param.idpDbAddr + ": " + param.idpDbTag + "." + param.idpDbTag2) default: return erro.New("invalid IdP DB type " + param.idpDbType) } // セッション。 var asessDb asession.Db switch param.asessDbType { case "memory": asessDb = asession.NewMemoryDb() log.Info("Save user sessions in memory") case "redis": asessDb = asession.NewRedisDb(redPools.Get(param.asessDbAddr), param.asessDbTag) log.Info("Save user sessions in redis " + param.asessDbAddr + ": " + param.asessDbTag) default: return erro.New("invalid user session DB type " + param.asessDbType) } // アクセストークン。 var tokDb token.Db switch param.tokDbType { case "memory": tokDb = token.NewMemoryDb() log.Info("Save access tokens in memory") case "redis": tokDb = token.NewRedisDb(redPools.Get(param.tokDbAddr), param.tokDbTag) log.Info("Save access tokens in redis " + param.tokDbAddr + ": " + param.tokDbTag) default: return erro.New("invalid access token DB type " + param.tokDbType) } var errTmpl *template.Template if param.tmplErr != "" { errTmpl, err = template.ParseFiles(param.tmplErr) if err != nil { return erro.Wrap(err) } } idGen := rand.New(time.Minute) var conn *http.Client if param.noVeri { conn = &http.Client{ // http.DefaultTransport を参考にした。 Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, Dial: (&net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, }).Dial, TLSHandshakeTimeout: 10 * time.Second, TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, }, } } else { conn = http.DefaultClient } // バックエンドの準備完了。 if param.debug { server.Debug = true } authPage := authpage.New( stopper, param.selfId, param.rediUri, param.sigAlg, errTmpl, param.asessLabel, param.asessLen, param.asessExpIn, param.asessDbExpIn, param.fsessLabel, param.fsessLen, param.fsessExpIn, param.statLen, param.noncLen, param.tokTagLen, param.tokDbExpIn, param.jtiLen, param.jtiExpIn, keyDb, idpDb, asessDb, tokDb, idGen, param.cookPath, param.cookSec, param.debug, ) mux := http.NewServeMux() routes := map[string]bool{} mux.HandleFunc(param.pathOk, server.WrapPage(stopper, func(w http.ResponseWriter, r *http.Request) error { return nil }, errTmpl)) routes[param.pathOk] = true mux.HandleFunc(param.pathAuth, authPage.HandleAuth) routes[param.pathAuth] = true mux.HandleFunc(param.pathCb, authPage.HandleCallback) routes[param.pathCb] = true mux.Handle(param.pathCoop, coop.New( stopper, param.selfId, param.sigAlg, param.sigKid, param.csessLabel, param.csessLen, param.tokTagLen, param.tokDbExpIn, param.jtiLen, param.jtiExpIn, keyDb, idpDb, tokDb, idGen, conn, param.cookPath, param.cookSec, param.debug, )) routes[param.pathCoop] = true if !routes["/"] { mux.HandleFunc("/", server.WrapPage(stopper, func(w http.ResponseWriter, r *http.Request) error { return erro.Wrap(server.NewError(http.StatusNotFound, "invalid endpoint", nil)) }, errTmpl)) } // サーバー設定完了。 defer func() { // 処理の終了待ち。 stopper.Lock() defer stopper.Unlock() for stopper.Stopped() { stopper.Wait() } }() return server.Serve(mux, param.socType, param.protType, param) }
// 認可コードを使って、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) 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 }