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
}