// 正常に認証 URI にリダイレクトさせることの検査。
// X-Auth-Uri が ID プロバイダなら response_type を code にすることの検査。
// scope を openid にすることの検査。
// client_id, redirect_uri, state, nonce が追加されることの検査。
// セッションが発行されることの検査。
func TestAuth(t *testing.T) {
	// ////////////////////////////////
	// logutil.SetupConsole(logRoot, level.ALL)
	// defer logutil.SetupConsole(logRoot, level.OFF)
	// ////////////////////////////////

	page := newTestPage([]jwk.Key{test_taKey}, []idpdb.Element{test_idp})

	r, err := newAuthRequest()
	if err != nil {
		t.Fatal(err)
	}

	w := httptest.NewRecorder()
	page.HandleAuth(w, r)

	if w.Code != http.StatusFound {
		t.Error(w.Code)
		t.Fatal(http.StatusFound)
	} else if uri, err := url.Parse(w.HeaderMap.Get("Location")); err != nil {
		t.Fatal(err)
	} else if authUri := uri.Scheme + "://" + uri.Host + uri.Path; authUri != test_idp.AuthUri() {
		t.Error(authUri)
		t.Fatal(test_idp.AuthUri())
	} else if ok, err := regexp.MatchString(page.sessLabel+"=[0-9a-zA-Z_\\-]", w.HeaderMap.Get("Set-Cookie")); err != nil {
		t.Fatal(err)
	} else if !ok {
		t.Error("no new session")
		t.Fatal(w.HeaderMap.Get("Set-Cookie"))
	} else if q := uri.Query(); len(q) == 0 {
		t.Fatal("no query")
	} else if respType := request.FormValueSet(q.Get("response_type")); len(respType) != 1 || !respType["code"] {
		t.Error(respType)
		t.Fatal("code")
	} else if scop := request.FormValueSet(q.Get("scope")); len(scop) != 1 || !scop["openid"] {
		t.Error(scop)
		t.Fatal("openid")
	} else if taId := q.Get("client_id"); taId != page.selfId {
		t.Error(taId)
		t.Fatal(page.selfId)
	} else if rediUri := q.Get("redirect_uri"); rediUri != page.rediUri {
		t.Error(rediUri)
		t.Fatal(page.rediUri)
	} else if q.Get("state") == "" {
		t.Fatal("no state")
	} else if q.Get("nonce") == "" {
		t.Fatal("no nonce")
	}
}
// X-Auth-Uri が ID プロバイダでないなら response_type を code id_token にすることの検査。
func TestAuthForSelector(t *testing.T) {
	// ////////////////////////////////
	// logutil.SetupConsole(logRoot, level.ALL)
	// defer logutil.SetupConsole(logRoot, level.OFF)
	// ////////////////////////////////

	page := newTestPage([]jwk.Key{test_taKey}, []idpdb.Element{test_idp})

	r, err := newAuthRequest()
	if err != nil {
		t.Fatal(err)
	}
	r.Header.Set("X-Auth-Uri", test_idp.Id()+"a/auth")

	w := httptest.NewRecorder()
	page.HandleAuth(w, r)

	if w.Code != http.StatusFound {
		t.Error(w.Code)
		t.Fatal(http.StatusFound)
	} else if uri, err := url.Parse(w.HeaderMap.Get("Location")); err != nil {
		t.Fatal(err)
	} else if q := uri.Query(); len(q) == 0 {
		t.Fatal("no query")
	} else if respType, respType2 := strsetutil.New("code", "id_token"), request.FormValueSet(q.Get("response_type")); !reflect.DeepEqual(respType2, respType) {
		t.Error(respType2)
		t.Fatal(respType)
	}
}
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 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
}
Beispiel #5
0
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)
	}
}
Beispiel #6
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
}