func()

in lib/ic/broker.go [200:320]


func (s *Service) doFinishLogin(r *http.Request) (_ *challenge, _ *htmlPageOrRedirectURL, ferr error) {
	r.ParseForm()

	tx, err := s.store.Tx(true)
	if err != nil {
		return nil, nil, status.Errorf(codes.Unavailable, "%v", err)
	}
	defer func() {
		err := tx.Finish()
		if ferr == nil && err != nil {
			ferr = status.Errorf(codes.Internal, "%v", err)
		}
	}()

	cfg, err := s.loadConfig(tx, getRealm(r))
	if err != nil {
		return nil, nil, status.Errorf(codes.Unavailable, "%v", err)
	}
	provider := getName(r)
	idp, ok := cfg.IdentityProviders[provider]
	if !ok {
		return nil, nil, status.Errorf(codes.Unauthenticated, "invalid identity provider %q", provider)
	}

	code := httputils.QueryParam(r, "code")
	stateParam := httputils.QueryParam(r, "state")
	idToken := ""
	accessToken := ""
	extract := ""
	// Experimental allows reading tokens from non-OIDC.
	if globalflags.Experimental {
		idToken = httputils.QueryParam(r, "id_token")
		accessToken = httputils.QueryParam(r, "access_token")
		extract = httputils.QueryParam(r, "client_extract") // makes sure we only grab state from client once

		if len(extract) == 0 && len(code) == 0 && len(idToken) == 0 && len(accessToken) == 0 {
			instructions := ""
			if len(idp.TokenUrl) > 0 && !strings.HasPrefix(idp.TokenUrl, "http") {
				// Allow the client login page to follow instructions encoded in the TokenUrl.
				// This enables support for some non-OIDC clients.
				instructions = idp.TokenUrl
			}
			args := &clientLoginPageArgs{
				AssetDir:     assetPath,
				Instructions: instructions,
			}
			sb := &strings.Builder{}
			if err := s.clientLoginPageTmpl.Execute(sb, args); err != nil {
				return nil, nil, status.Errorf(codes.Internal, "render client login page failed: %v", err)
			}
			return nil, &htmlPageOrRedirectURL{page: sb.String()}, nil
		}
	} else {
		// Experimental allows non OIDC auth code flow which code or stateParam can be empty.
		if len(code) == 0 || len(stateParam) == 0 {
			return nil, nil, status.Errorf(codes.Unauthenticated, "query params code or state missing")
		}
	}

	loginState := &cpb.LoginState{}
	err = s.store.ReadTx(storage.LoginStateDatatype, storage.DefaultRealm, storage.DefaultUser, stateParam, storage.LatestRev, loginState, tx)
	if err != nil {
		return nil, nil, status.Errorf(codes.Internal, "read login state failed, %q", err)
	}

	if s.useHydra {
		if len(loginState.LoginChallenge) == 0 {
			return nil, nil, status.Errorf(codes.Unauthenticated, "invalid login state parameter")
		}
	} else {
		return nil, nil, status.Errorf(codes.Unimplemented, "Unimplemented oidc provider")
	}

	challenge := &challenge{
		login:   loginState.LoginChallenge,
		consent: loginState.ConsentChallenge,
	}

	if loginState.Step != cpb.LoginState_LOGIN {
		return challenge, nil, status.Errorf(codes.Unauthenticated, "login state not in login step")
	}

	if len(loginState.Provider) == 0 || len(loginState.Realm) == 0 {
		return challenge, nil, status.Errorf(codes.Unauthenticated, "invalid login state parameter")
	}

	if len(code) == 0 && len(idToken) == 0 && !s.idpUsesClientLoginPage(loginState.Provider, loginState.Realm, cfg) {
		return challenge, nil, status.Errorf(codes.Unauthenticated, "missing auth code")
	}

	if provider != loginState.Provider {
		return challenge, nil, status.Errorf(codes.Unauthenticated, "request idp does not match login state, want %q, got %q", loginState.Provider, provider)
	}

	secrets, err := s.loadSecrets(tx)
	if err != nil {
		return challenge, nil, status.Errorf(codes.Unavailable, "%v", err)
	}
	if len(accessToken) == 0 {
		idpc := idpConfig(idp, s.getDomainURL(), secrets)
		tok, err := idpc.Exchange(r.Context(), code)
		if err != nil {
			return challenge, nil, status.Errorf(codes.Unauthenticated, "invalid code: %v", err)
		}
		accessToken = tok.AccessToken
		if len(idToken) == 0 {
			idToken, ok = tok.Extra("id_token").(string)
			if !ok && len(accessToken) == 0 {
				return challenge, nil, status.Errorf(codes.Unauthenticated, "identity provider response does not contain an access_token nor id_token token")
			}
		}
	}

	login, st, err := s.loginTokenToIdentity(accessToken, idToken, idp, r, cfg, secrets)
	if err != nil {
		return challenge, nil, status.Errorf(httputils.RPCCode(st), "%v", err)
	}

	res, err := s.finishLogin(login, stateParam, loginState, tx, cfg, secrets, r)
	return challenge, res, err
}