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
}
Example #2
0
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)
}
Example #3
0
// 認可コードを使って、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
}
Example #4
0
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
}