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 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 (this *idToken) verifyTokenHash(tok string) (err error) { hGen := jwt.HashGenerator(this.alg) if !hGen.Available() { return erro.New("unsupported algorithm " + this.alg) } hVal := hash.Hashing(hGen.New(), []byte(tok)) if !bytes.Equal(this.atHash, hVal[:len(hVal)/2]) { return erro.New("verification failed") } return nil }
func TestLockConcurrency(t *testing.T) { file, err := ioutil.TempFile("", "go-lib-test") if err != nil { t.Fatal(err) } defer os.Remove(file.Name()) path := file.Name() if err := file.Close(); err != nil { t.Fatal(err) } else if err := os.Remove(path); err != nil { t.Fatal(err) } counter := 0 errCh := make(chan error) n := 100 loop := 100 for i := 0; i < n; i++ { go func(id int) { for j := 0; j < loop; j++ { lock, err := Lock(path) if err != nil { errCh <- erro.New(err) return } counter++ if err := lock.Unlock(); err != nil { errCh <- erro.New(err) return } } errCh <- nil }(i) } // 終了待ち。 for i := 0; i < n; i++ { if err := <-errCh; err != nil { t.Fatal(err) } } if counter != n*loop { t.Fatal(counter, n*loop) } }
func (this *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { var logPref string // panic 対策。 defer func() { if rcv := recover(); rcv != nil { w.Header().Set(tagX_edo_cooperation_error, fmt.Sprint(rcv)) idperr.RespondJson(w, r, erro.New(rcv), logPref) return } }() if this.stopper != nil { this.stopper.Stop() defer this.stopper.Unstop() } logPref = server.ParseSender(r) + ": " server.LogRequest(level.DEBUG, r, this.debug, logPref) log.Info(logPref, "Received cooperation request") defer log.Info(logPref, "Handled cooperation request") if err := (&environment{this, logPref}).serve(w, r); err != nil { w.Header().Set(tagX_edo_cooperation_error, idperr.From(err).ErrorDescription()) idperr.RespondJson(w, r, erro.Wrap(err), logPref) return } }
func (this *Page) HandleCallback(w http.ResponseWriter, r *http.Request) { var logPref string // panic 対策。 defer func() { if rcv := recover(); rcv != nil { server.RespondErrorHtml(w, r, erro.New(rcv), this.errTmpl, logPref) return } }() if this.stopper != nil { this.stopper.Stop() defer this.stopper.Unstop() } sender := request.Parse(r, this.sessLabel) logPref = sender.String() + ": " server.LogRequest(level.DEBUG, r, this.debug, logPref) log.Info(logPref, "Received callback request") defer log.Info(logPref, "Handled callback request") if err := (&environment{this, logPref, sender.Session(), nil}).callbackServe(w, r); err != nil { server.RespondErrorHtml(w, r, erro.Wrap(err), this.errTmpl, logPref) return } return }
// ユーザー認証開始。 func (this *Page) HandleAuth(w http.ResponseWriter, r *http.Request) { var logPref string // panic 対策。 defer func() { if rcv := recover(); rcv != nil { server.RespondErrorHtml(w, r, erro.New(rcv), this.errTmpl, logPref) return } }() if this.stopper != nil { this.stopper.Stop() defer this.stopper.Unstop() } logPref = server.ParseSender(r) + ": " server.LogRequest(level.DEBUG, r, this.debug, logPref) log.Info(logPref, "Received authentication request") defer log.Info(logPref, "Handled authentication request") if err := (&environment{this, logPref, "", nil}).authServe(w, r); err != nil { server.RespondErrorHtml(w, r, erro.Wrap(err), this.errTmpl, logPref) return } return }
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 ValueOf(label string) (Level, error) { lv, ok := labelToLv[label] if ok { return lv, nil } else { return 0, erro.New("level " + label + " is not exist") } }
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 parseRequest(r *http.Request) (*request, error) { rawCodToks := r.Header[tagX_edo_code_tokens] if rawCodToks == nil { rawCodToks = requtil.FormValues(r.FormValue(tagCode_tokens)) } if len(rawCodToks) == 0 { return nil, erro.New("no cooperation codes") } codToks := [][]byte{} for _, rawCodTok := range rawCodToks { codToks = append(codToks, []byte(rawCodTok)) } return &request{codToks}, nil }
func parseCallbackRequest(r *http.Request) (*callbackRequest, error) { cod := r.FormValue(tagCode) if cod == "" { return nil, erro.New("no code") } var idTok []byte if rawIdTok := r.FormValue(tagId_token); rawIdTok != "" { idTok = []byte(rawIdTok) } return &callbackRequest{ cod, r.FormValue(tagState), idTok, }, nil }
func parseAuthRequest(r *http.Request) (req *authRequest, err error) { var authUri *url.URL if rawAuthUri := r.Header.Get(tagX_auth_uri); rawAuthUri == "" { return nil, erro.New("no authentication uri") } else { authUri, err = url.Parse(rawAuthUri) if err != nil { return nil, erro.Wrap(err) } } path := r.URL.Path if r.URL.RawQuery != "" { path += "?" + r.URL.RawQuery } return &authRequest{ authUri, path, }, nil }
func (this *redisDb) Replace(elem *Element, savedDate time.Time) (ok bool, err error) { conn := this.pool.Get() defer conn.Close() data, err := json.Marshal(elem) if err != nil { return false, erro.Wrap(err) } res, err := redis.String(replaceScript.Do(conn, this.tag+elem.Id(), this.tag+tagDate+elem.Id(), savedDate.UnixNano(), data, elem.Date().UnixNano())) if err != nil { return false, erro.Wrap(err) } else if res != "" { if res == "invalid date" { return false, nil } return false, erro.New(res) } return true, 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 }
func waitServer(uri string, errCh chan error, exp time.Time) error { for { if time.Now().After(exp) { return erro.New("timeout") } r, err := http.NewRequest("GET", uri, nil) if err != nil { return erro.Wrap(err) } r.Header.Set("Connection", "close") if _, err := http.DefaultClient.Do(r); err == nil { break } select { case err := <-errCh: return erro.Wrap(err) default: } time.Sleep(time.Millisecond) } return 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 }
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) }