zitadel/internal/command/user_human_webauthn.go
Silvan dabd5920dc
feat: protos refactoring
* start with user

* user first try done in all services

* user, org, idp for discussion

* remove unused stuff

* bla

* dockerbuild

* rename search, get multiple to list...

* add annotation

* update proto dependencies

* update proto dependencies

* change proto imports

* replace all old imports

* fix go out

* remove unused lines

* correct protoc flags

* grpc and openapi flags

* go out source path relative

* -p

* remove dead code

* sourcepath relative

* ls

* is onenapi the problem?

* hobla

* authoption output

* wrong field name

* gopf

* correct option, add correct flags

* small improvments

* SIMPLYFY

* relative path

* gopf bin ich en tubel

* correct path

* default policies in admin

* grpc generation in one file

* remove non ascii

* metadata on manipulations

* correct auth_option import

* fixes

* larry

* idp provider to idp

* fix generate

* admin and auth nearly done

* admin and auth nearly done

* gen

* healthz

* imports

* deleted too much imports

* fix org

* add import

* imports

* import

* naming

* auth_opt

* gopf

* management

* imports

* _TYPE_UNSPECIFIED

* improts

* auth opts

* management policies

* imports

* passwordlessType to MFAType

* auth_opt

* add user grant calls

* add missing messages

* result

* fix option

* improvements

* ids

* fix http

* imports

* fixes

* fields

* body

* add fields

* remove wrong member query

* fix request response

* fixes

* add copy files

* variable versions

* generate all files

* improvements

* add dependencies

* factors

* user session

* oidc information, iam

* remove unused file

* changes

* enums

* dockerfile

* fix build

* remove unused folder

* update readme for build

* move old server impl

* add event type to change

* some changes

* start admin

* remove wrong field

* admin only list calls missing

* fix proto numbers

* surprisingly it compiles

* service ts changes

* admin mgmt

* mgmt

* auth manipulation and gets done, lists missing

* validations and some field changes

* validations

* enum validations

* remove todo

* move proto files to proto/zitadel

* change proto path in dockerfile

* it compiles!

* add validate import

* remove duplicate import

* fix protos

* fix import

* tests

* cleanup

* remove unimplemented methods

* iam member multiple queries

* all auth and admin calls

* add initial password on crate human

* message names

* management user server

* machine done

* fix: todos (#1346)

* fix: pub sub in new eventstore

* fix: todos

* fix: todos

* fix: todos

* fix: todos

* fix: todos

* fix tests

* fix: search method domain

* admin service, user import type typescript

* admin changes

* admin changes

* fix: search method domain

* more user grpc and begin org, fix configs

* fix: return object details

* org grpc

* remove creation date add details

* app

* fix: return object details

* fix: return object details

* mgmt service, project members

* app

* fix: convert policies

* project, members, granted projects, searches

* fix: convert usergrants

* fix: convert usergrants

* auth user detail, user detail, mfa, second factor, auth

* fix: convert usergrants

* mfa, memberships, password, owned proj detail

* fix: convert usergrants

* project grant

* missing details

* changes, userview

* idp table, keys

* org list and user table filter

* unify rest paths (#1381)

* unify rest paths

* post for all searches,
mfa to multi_factor,
secondfactor to second_factor

* remove v1

* fix tests

* rename api client key to app key

* machine keys, age policy

* user list, machine keys, changes

* fix: org states

* add default flag to policy

* second factor to type

* idp id

* app type

* unify ListQuery, ListDetails, ObjectDetails field names

* user grants, apps, memberships

* fix type params

* metadata to detail, linke idps

* api create, membership, app detail, create

* idp, app, policy

* queries, multi -> auth factors and missing fields

* update converters

* provider to user, remove old mgmt refs

* temp remove authfactor dialog, build finish

Co-authored-by: Max Peintner <max@caos.ch>
Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com>
Co-authored-by: Livio Amstutz <livio.a@gmail.com>
Co-authored-by: Fabiennne <fabienne.gerschwiler@gmail.com>
2021-03-09 10:30:11 +01:00

465 lines
16 KiB
Go

package command
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
usr_repo "github.com/caos/zitadel/internal/repository/user"
"github.com/caos/zitadel/internal/telemetry/tracing"
)
func (c *Commands) getHumanU2FTokens(ctx context.Context, userID, resourceowner string) ([]*domain.WebAuthNToken, error) {
tokenReadModel := NewHumanU2FTokensReadModel(userID, resourceowner)
err := c.eventstore.FilterToQueryReducer(ctx, tokenReadModel)
if err != nil {
return nil, err
}
if tokenReadModel.UserState == domain.UserStateDeleted {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-4M0ds", "Errors.User.NotFound")
}
return readModelToU2FTokens(tokenReadModel), nil
}
func (c *Commands) getHumanPasswordlessTokens(ctx context.Context, userID, resourceowner string) ([]*domain.WebAuthNToken, error) {
tokenReadModel := NewHumanPasswordlessTokensReadModel(userID, resourceowner)
err := c.eventstore.FilterToQueryReducer(ctx, tokenReadModel)
if err != nil {
return nil, err
}
if tokenReadModel.UserState == domain.UserStateDeleted {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-Mv9sd", "Errors.User.NotFound")
}
return readModelToPasswordlessTokens(tokenReadModel), nil
}
func (c *Commands) getHumanU2FLogin(ctx context.Context, userID, authReqID, resourceowner string) (*domain.WebAuthNLogin, error) {
tokenReadModel := NewHumanU2FLoginReadModel(userID, authReqID, resourceowner)
err := c.eventstore.FilterToQueryReducer(ctx, tokenReadModel)
if err != nil {
return nil, err
}
if tokenReadModel.State == domain.UserStateDeleted {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-5m88U", "Errors.User.NotFound")
}
return &domain.WebAuthNLogin{
Challenge: tokenReadModel.Challenge,
}, nil
}
func (c *Commands) getHumanPasswordlessLogin(ctx context.Context, userID, authReqID, resourceowner string) (*domain.WebAuthNLogin, error) {
tokenReadModel := NewHumanPasswordlessLoginReadModel(userID, authReqID, resourceowner)
err := c.eventstore.FilterToQueryReducer(ctx, tokenReadModel)
if err != nil {
return nil, err
}
if tokenReadModel.State == domain.UserStateDeleted {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-fm84R", "Errors.User.NotFound")
}
return &domain.WebAuthNLogin{
Challenge: tokenReadModel.Challenge,
}, nil
}
func (c *Commands) HumanAddU2FSetup(ctx context.Context, userID, resourceowner string, isLoginUI bool) (*domain.WebAuthNToken, error) {
u2fTokens, err := c.getHumanU2FTokens(ctx, userID, resourceowner)
if err != nil {
return nil, err
}
addWebAuthN, userAgg, webAuthN, err := c.addHumanWebAuthN(ctx, userID, resourceowner, isLoginUI, u2fTokens)
if err != nil {
return nil, err
}
events, err := c.eventstore.PushEvents(ctx, usr_repo.NewHumanU2FAddedEvent(ctx, userAgg, addWebAuthN.WebauthNTokenID, webAuthN.Challenge))
if err != nil {
return nil, err
}
err = AppendAndReduce(addWebAuthN, events...)
if err != nil {
return nil, err
}
createdWebAuthN := writeModelToWebAuthN(addWebAuthN)
createdWebAuthN.CredentialCreationData = webAuthN.CredentialCreationData
createdWebAuthN.AllowedCredentialIDs = webAuthN.AllowedCredentialIDs
createdWebAuthN.UserVerification = webAuthN.UserVerification
return createdWebAuthN, nil
}
func (c *Commands) HumanAddPasswordlessSetup(ctx context.Context, userID, resourceowner string, isLoginUI bool) (*domain.WebAuthNToken, error) {
passwordlessTokens, err := c.getHumanPasswordlessTokens(ctx, userID, resourceowner)
if err != nil {
return nil, err
}
addWebAuthN, userAgg, webAuthN, err := c.addHumanWebAuthN(ctx, userID, resourceowner, isLoginUI, passwordlessTokens)
if err != nil {
return nil, err
}
events, err := c.eventstore.PushEvents(ctx, usr_repo.NewHumanPasswordlessAddedEvent(ctx, userAgg, addWebAuthN.WebauthNTokenID, webAuthN.Challenge))
if err != nil {
return nil, err
}
err = AppendAndReduce(addWebAuthN, events...)
if err != nil {
return nil, err
}
createdWebAuthN := writeModelToWebAuthN(addWebAuthN)
createdWebAuthN.CredentialCreationData = webAuthN.CredentialCreationData
createdWebAuthN.AllowedCredentialIDs = webAuthN.AllowedCredentialIDs
createdWebAuthN.UserVerification = webAuthN.UserVerification
return createdWebAuthN, nil
}
func (c *Commands) addHumanWebAuthN(ctx context.Context, userID, resourceowner string, isLoginUI bool, tokens []*domain.WebAuthNToken) (*HumanWebAuthNWriteModel, *eventstore.Aggregate, *domain.WebAuthNToken, error) {
if userID == "" || resourceowner == "" {
return nil, nil, nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3M0od", "Errors.IDMissing")
}
user, err := c.getHuman(ctx, userID, resourceowner)
if err != nil {
return nil, nil, nil, err
}
org, err := c.getOrg(ctx, user.ResourceOwner)
if err != nil {
return nil, nil, nil, err
}
orgPolicy, err := c.getOrgIAMPolicy(ctx, org.AggregateID)
if err != nil {
return nil, nil, nil, err
}
accountName := domain.GenerateLoginName(user.GetUsername(), org.PrimaryDomain, orgPolicy.UserLoginMustBeDomain)
if accountName == "" {
accountName = user.EmailAddress
}
webAuthN, err := c.webauthn.BeginRegistration(user, accountName, domain.AuthenticatorAttachmentUnspecified, domain.UserVerificationRequirementDiscouraged, isLoginUI, tokens...)
if err != nil {
return nil, nil, nil, err
}
tokenID, err := c.idGenerator.Next()
if err != nil {
return nil, nil, nil, err
}
addWebAuthN, err := c.webauthNWriteModelByID(ctx, userID, tokenID, resourceowner)
if err != nil {
return nil, nil, nil, err
}
userAgg := UserAggregateFromWriteModel(&addWebAuthN.WriteModel)
return addWebAuthN, userAgg, webAuthN, nil
}
func (c *Commands) HumanVerifyU2FSetup(ctx context.Context, userID, resourceowner, tokenName, userAgentID string, credentialData []byte) (*domain.ObjectDetails, error) {
u2fTokens, err := c.getHumanU2FTokens(ctx, userID, resourceowner)
if err != nil {
return nil, err
}
userAgg, webAuthN, verifyWebAuthN, err := c.verifyHumanWebAuthN(ctx, userID, resourceowner, tokenName, userAgentID, credentialData, u2fTokens)
if err != nil {
return nil, err
}
pushedEvents, err := c.eventstore.PushEvents(ctx,
usr_repo.NewHumanU2FVerifiedEvent(
ctx,
userAgg,
verifyWebAuthN.WebauthNTokenID,
webAuthN.WebAuthNTokenName,
webAuthN.AttestationType,
webAuthN.KeyID,
webAuthN.PublicKey,
webAuthN.AAGUID,
webAuthN.SignCount,
),
)
if err != nil {
return nil, err
}
err = AppendAndReduce(verifyWebAuthN, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&verifyWebAuthN.WriteModel), nil
}
func (c *Commands) HumanHumanPasswordlessSetup(ctx context.Context, userID, resourceowner, tokenName, userAgentID string, credentialData []byte) (*domain.ObjectDetails, error) {
u2fTokens, err := c.getHumanPasswordlessTokens(ctx, userID, resourceowner)
if err != nil {
return nil, err
}
userAgg, webAuthN, verifyWebAuthN, err := c.verifyHumanWebAuthN(ctx, userID, resourceowner, tokenName, userAgentID, credentialData, u2fTokens)
if err != nil {
return nil, err
}
pushedEvents, err := c.eventstore.PushEvents(ctx,
usr_repo.NewHumanPasswordlessVerifiedEvent(
ctx,
userAgg,
verifyWebAuthN.WebauthNTokenID,
webAuthN.WebAuthNTokenName,
webAuthN.AttestationType,
webAuthN.KeyID,
webAuthN.PublicKey,
webAuthN.AAGUID,
webAuthN.SignCount,
),
)
if err != nil {
return nil, err
}
err = AppendAndReduce(verifyWebAuthN, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&verifyWebAuthN.WriteModel), nil
}
func (c *Commands) verifyHumanWebAuthN(ctx context.Context, userID, resourceowner, tokenName, userAgentID string, credentialData []byte, tokens []*domain.WebAuthNToken) (*eventstore.Aggregate, *domain.WebAuthNToken, *HumanWebAuthNWriteModel, error) {
if userID == "" || resourceowner == "" {
return nil, nil, nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3M0od", "Errors.IDMissing")
}
user, err := c.getHuman(ctx, userID, resourceowner)
if err != nil {
return nil, nil, nil, err
}
_, token := domain.GetTokenToVerify(tokens)
webAuthN, err := c.webauthn.FinishRegistration(user, token, tokenName, credentialData, userAgentID != "")
if err != nil {
return nil, nil, nil, err
}
verifyWebAuthN, err := c.webauthNWriteModelByID(ctx, userID, token.WebAuthNTokenID, resourceowner)
if err != nil {
return nil, nil, nil, err
}
userAgg := UserAggregateFromWriteModel(&verifyWebAuthN.WriteModel)
return userAgg, webAuthN, verifyWebAuthN, nil
}
func (c *Commands) HumanBeginU2FLogin(ctx context.Context, userID, resourceOwner string, authRequest *domain.AuthRequest, isLoginUI bool) (*domain.WebAuthNLogin, error) {
u2fTokens, err := c.getHumanU2FTokens(ctx, userID, resourceOwner)
if err != nil {
return nil, err
}
userAgg, webAuthNLogin, err := c.beginWebAuthNLogin(ctx, userID, resourceOwner, u2fTokens, isLoginUI)
if err != nil {
return nil, err
}
_, err = c.eventstore.PushEvents(ctx,
usr_repo.NewHumanU2FBeginLoginEvent(
ctx,
userAgg,
webAuthNLogin.Challenge,
authRequestDomainToAuthRequestInfo(authRequest),
),
)
return webAuthNLogin, err
}
func (c *Commands) HumanBeginPasswordlessLogin(ctx context.Context, userID, resourceOwner string, authRequest *domain.AuthRequest, isLoginUI bool) (*domain.WebAuthNLogin, error) {
u2fTokens, err := c.getHumanPasswordlessTokens(ctx, userID, resourceOwner)
if err != nil {
return nil, err
}
userAgg, webAuthNLogin, err := c.beginWebAuthNLogin(ctx, userID, resourceOwner, u2fTokens, isLoginUI)
if err != nil {
return nil, err
}
_, err = c.eventstore.PushEvents(ctx,
usr_repo.NewHumanPasswordlessBeginLoginEvent(
ctx,
userAgg,
webAuthNLogin.Challenge,
authRequestDomainToAuthRequestInfo(authRequest),
),
)
return webAuthNLogin, err
}
func (c *Commands) beginWebAuthNLogin(ctx context.Context, userID, resourceOwner string, tokens []*domain.WebAuthNToken, isLoginUI bool) (*eventstore.Aggregate, *domain.WebAuthNLogin, error) {
if userID == "" {
return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-hh8K9", "Errors.IDMissing")
}
human, err := c.getHuman(ctx, userID, resourceOwner)
if err != nil {
return nil, nil, err
}
webAuthNLogin, err := c.webauthn.BeginLogin(human, domain.UserVerificationRequirementDiscouraged, isLoginUI, tokens...)
if err != nil {
return nil, nil, err
}
writeModel, err := c.webauthNWriteModelByID(ctx, userID, "", resourceOwner)
if err != nil {
return nil, nil, err
}
userAgg := UserAggregateFromWriteModel(&writeModel.WriteModel)
return userAgg, webAuthNLogin, nil
}
func (c *Commands) HumanFinishU2FLogin(ctx context.Context, userID, resourceOwner string, credentialData []byte, authRequest *domain.AuthRequest, isLoginUI bool) error {
webAuthNLogin, err := c.getHumanU2FLogin(ctx, userID, authRequest.ID, resourceOwner)
if err != nil {
return err
}
u2fTokens, err := c.getHumanU2FTokens(ctx, userID, resourceOwner)
if err != nil {
return err
}
userAgg, token, signCount, err := c.finishWebAuthNLogin(ctx, userID, resourceOwner, credentialData, webAuthNLogin, u2fTokens, isLoginUI)
if err != nil {
_, pushErr := c.eventstore.PushEvents(ctx,
usr_repo.NewHumanU2FCheckFailedEvent(
ctx,
userAgg,
authRequestDomainToAuthRequestInfo(authRequest),
),
)
logging.Log("EVENT-33M9f").OnError(pushErr).WithField("userID", userID).Warn("could not push failed passwordless check event")
return err
}
_, err = c.eventstore.PushEvents(ctx,
usr_repo.NewHumanU2FCheckSucceededEvent(
ctx,
userAgg,
authRequestDomainToAuthRequestInfo(authRequest),
),
usr_repo.NewHumanU2FSignCountChangedEvent(
ctx,
userAgg,
token.WebAuthNTokenID,
signCount,
),
)
return err
}
func (c *Commands) HumanFinishPasswordlessLogin(ctx context.Context, userID, resourceOwner string, credentialData []byte, authRequest *domain.AuthRequest, isLoginUI bool) error {
webAuthNLogin, err := c.getHumanPasswordlessLogin(ctx, userID, authRequest.ID, resourceOwner)
if err != nil {
return err
}
passwordlessTokens, err := c.getHumanPasswordlessTokens(ctx, userID, resourceOwner)
if err != nil {
return err
}
userAgg, token, signCount, err := c.finishWebAuthNLogin(ctx, userID, resourceOwner, credentialData, webAuthNLogin, passwordlessTokens, isLoginUI)
if err != nil {
_, pushErr := c.eventstore.PushEvents(ctx,
usr_repo.NewHumanPasswordlessCheckFailedEvent(
ctx,
userAgg,
authRequestDomainToAuthRequestInfo(authRequest),
),
)
logging.Log("EVENT-33M9f").OnError(pushErr).WithField("userID", userID).Warn("could not push failed passwordless check event")
return err
}
_, err = c.eventstore.PushEvents(ctx,
usr_repo.NewHumanU2FCheckSucceededEvent(
ctx,
userAgg,
authRequestDomainToAuthRequestInfo(authRequest),
),
usr_repo.NewHumanPasswordlessSignCountChangedEvent(
ctx,
userAgg,
token.WebAuthNTokenID,
signCount,
),
)
return err
}
func (c *Commands) finishWebAuthNLogin(ctx context.Context, userID, resourceOwner string, credentialData []byte, webAuthN *domain.WebAuthNLogin, tokens []*domain.WebAuthNToken, isLoginUI bool) (*eventstore.Aggregate, *domain.WebAuthNToken, uint32, error) {
if userID == "" {
return nil, nil, 0, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-hh8K9", "Errors.IDMissing")
}
human, err := c.getHuman(ctx, userID, resourceOwner)
if err != nil {
return nil, nil, 0, err
}
keyID, signCount, err := c.webauthn.FinishLogin(human, webAuthN, credentialData, isLoginUI, tokens...)
if err != nil && keyID == nil {
return nil, nil, 0, err
}
_, token := domain.GetTokenByKeyID(tokens, keyID)
if token == nil {
return nil, nil, 0, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3b7zs", "Errors.User.WebAuthN.NotFound")
}
writeModel, err := c.webauthNWriteModelByID(ctx, userID, "", resourceOwner)
if err != nil {
return nil, nil, 0, err
}
userAgg := UserAggregateFromWriteModel(&writeModel.WriteModel)
return userAgg, token, signCount, nil
}
func (c *Commands) HumanRemoveU2F(ctx context.Context, userID, webAuthNID, resourceOwner string) (*domain.ObjectDetails, error) {
event := usr_repo.PrepareHumanU2FRemovedEvent(ctx, webAuthNID)
return c.removeHumanWebAuthN(ctx, userID, webAuthNID, resourceOwner, event)
}
func (c *Commands) HumanRemovePasswordless(ctx context.Context, userID, webAuthNID, resourceOwner string) (*domain.ObjectDetails, error) {
event := usr_repo.PrepareHumanPasswordlessRemovedEvent(ctx, webAuthNID)
return c.removeHumanWebAuthN(ctx, userID, webAuthNID, resourceOwner, event)
}
func (c *Commands) removeHumanWebAuthN(ctx context.Context, userID, webAuthNID, resourceOwner string, preparedEvent func(*eventstore.Aggregate) eventstore.EventPusher) (*domain.ObjectDetails, error) {
if userID == "" || webAuthNID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6M9de", "Errors.IDMissing")
}
existingWebAuthN, err := c.webauthNWriteModelByID(ctx, userID, webAuthNID, resourceOwner)
if err != nil {
return nil, err
}
if existingWebAuthN.State == domain.MFAStateUnspecified || existingWebAuthN.State == domain.MFAStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-2M9ds", "Errors.User.ExternalIDP.NotFound")
}
userAgg := UserAggregateFromWriteModel(&existingWebAuthN.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, preparedEvent(userAgg))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingWebAuthN, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingWebAuthN.WriteModel), nil
}
func (c *Commands) webauthNWriteModelByID(ctx context.Context, userID, webAuthNID, resourceOwner string) (writeModel *HumanWebAuthNWriteModel, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
writeModel = NewHumanWebAuthNWriteModel(userID, webAuthNID, resourceOwner)
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
if err != nil {
return nil, err
}
return writeModel, nil
}