fix: login (#242)

* password in init user only if needed

* reactivate user session

* set context AuthorizeClientIDSecret

* fix qr code for light

* fix copy

* check user and org active in auth

* add org view provider

* handle inactive projects

* translate error messages
This commit is contained in:
Livio Amstutz
2020-06-19 14:52:04 +02:00
committed by GitHub
parent fb89241984
commit e653eaab86
26 changed files with 327 additions and 83 deletions

View File

@@ -12,10 +12,12 @@ import (
"github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/id"
org_model "github.com/caos/zitadel/internal/org/model"
org_view_model "github.com/caos/zitadel/internal/org/repository/view/model"
user_model "github.com/caos/zitadel/internal/user/model"
user_event "github.com/caos/zitadel/internal/user/repository/eventsourcing"
es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
view_model "github.com/caos/zitadel/internal/user/repository/view/model"
user_view_model "github.com/caos/zitadel/internal/user/repository/view/model"
)
type AuthRequestRepo struct {
@@ -26,6 +28,7 @@ type AuthRequestRepo struct {
UserSessionViewProvider userSessionViewProvider
UserViewProvider userViewProvider
UserEventProvider userEventProvider
OrgViewProvider orgViewProvider
IdGenerator id.Generator
@@ -36,17 +39,21 @@ type AuthRequestRepo struct {
}
type userSessionViewProvider interface {
UserSessionByIDs(string, string) (*view_model.UserSessionView, error)
UserSessionsByAgentID(string) ([]*view_model.UserSessionView, error)
UserSessionByIDs(string, string) (*user_view_model.UserSessionView, error)
UserSessionsByAgentID(string) ([]*user_view_model.UserSessionView, error)
}
type userViewProvider interface {
UserByID(string) (*view_model.UserView, error)
UserByID(string) (*user_view_model.UserView, error)
}
type userEventProvider interface {
UserEventsByID(ctx context.Context, id string, sequence uint64) ([]*es_models.Event, error)
}
type orgViewProvider interface {
OrgByID(string) (*org_view_model.OrgView, error)
}
func (repo *AuthRequestRepo) Health(ctx context.Context) error {
if err := repo.UserEvents.Health(ctx); err != nil {
return err
@@ -124,7 +131,7 @@ func (repo *AuthRequestRepo) SelectUser(ctx context.Context, id, userID string)
if err != nil {
return err
}
user, err := repo.View.UserByID(userID)
user, err := activeUserByID(ctx, repo.UserViewProvider, repo.UserEventProvider, repo.OrgViewProvider, userID)
if err != nil {
return err
}
@@ -169,7 +176,7 @@ func (repo *AuthRequestRepo) getAuthRequest(ctx context.Context, id string, chec
func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *model.AuthRequest, checkLoggedIn bool) ([]model.NextStep, error) {
if request == nil {
return nil, errors.ThrowInvalidArgument(nil, "EVENT-ds27a", "request must not be nil")
return nil, errors.ThrowInvalidArgument(nil, "EVENT-ds27a", "Errors.Internal")
}
steps := make([]model.NextStep, 0)
if !checkLoggedIn && request.Prompt == model.PromptNone {
@@ -186,7 +193,7 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *model.AuthR
}
return steps, nil
}
user, err := userByID(ctx, repo.UserViewProvider, repo.UserEventProvider, request.UserID)
user, err := activeUserByID(ctx, repo.UserViewProvider, repo.UserEventProvider, repo.OrgViewProvider, request.UserID)
if err != nil {
return nil, err
}
@@ -296,7 +303,7 @@ func userSessionsByUserAgentID(provider userSessionViewProvider, agentID string)
if err != nil {
return nil, err
}
return view_model.UserSessionsToModel(session), nil
return user_view_model.UserSessionsToModel(session), nil
}
func userSessionByIDs(ctx context.Context, provider userSessionViewProvider, eventProvider userEventProvider, agentID string, user *user_model.UserView) (*user_model.UserSessionView, error) {
@@ -305,12 +312,12 @@ func userSessionByIDs(ctx context.Context, provider userSessionViewProvider, eve
if !errors.IsNotFound(err) {
return nil, err
}
session = &view_model.UserSessionView{}
session = &user_view_model.UserSessionView{}
}
events, err := eventProvider.UserEventsByID(ctx, user.ID, session.Sequence)
if err != nil {
logging.Log("EVENT-Hse6s").WithError(err).Debug("error retrieving new events")
return view_model.UserSessionToModel(session), nil
return user_view_model.UserSessionToModel(session), nil
}
sessionCopy := *session
for _, event := range events {
@@ -319,19 +326,44 @@ func userSessionByIDs(ctx context.Context, provider userSessionViewProvider, eve
es_model.UserPasswordCheckFailed,
es_model.MfaOtpCheckSucceeded,
es_model.MfaOtpCheckFailed,
es_model.SignedOut:
eventData, err := view_model.UserSessionFromEvent(event)
es_model.SignedOut,
es_model.UserLocked,
es_model.UserDeactivated:
eventData, err := user_view_model.UserSessionFromEvent(event)
if err != nil {
logging.Log("EVENT-sdgT3").WithError(err).Debug("error getting event data")
return view_model.UserSessionToModel(session), nil
return user_view_model.UserSessionToModel(session), nil
}
if eventData.UserAgentID != agentID {
continue
}
case es_model.UserRemoved:
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dG2fe", "Errors.User.NotActive")
}
sessionCopy.AppendEvent(event)
}
return view_model.UserSessionToModel(&sessionCopy), nil
return user_view_model.UserSessionToModel(&sessionCopy), nil
}
func activeUserByID(ctx context.Context, userViewProvider userViewProvider, userEventProvider userEventProvider, orgViewProvider orgViewProvider, userID string) (*user_model.UserView, error) {
user, err := userByID(ctx, userViewProvider, userEventProvider, userID)
if err != nil {
return nil, err
}
if user.State == user_model.USERSTATE_LOCKED || user.State == user_model.USERSTATE_SUSPEND {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-FJ262", "Errors.User.Locked")
}
if !(user.State == user_model.USERSTATE_ACTIVE || user.State == user_model.USERSTATE_INITIAL) {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-FJ262", "Errors.User.NotActive")
}
org, err := orgViewProvider.OrgByID(user.ResourceOwner)
if err != nil {
return nil, err
}
if org.State != int32(org_model.ORGSTATE_ACTIVE) {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-Zws3s", "Errors.User.NotActive")
}
return user, nil
}
func userByID(ctx context.Context, viewProvider userViewProvider, eventProvider userEventProvider, userID string) (*user_model.UserView, error) {
@@ -342,13 +374,13 @@ func userByID(ctx context.Context, viewProvider userViewProvider, eventProvider
events, err := eventProvider.UserEventsByID(ctx, userID, user.Sequence)
if err != nil {
logging.Log("EVENT-dfg42").WithError(err).Debug("error retrieving new events")
return view_model.UserToModel(user), nil
return user_view_model.UserToModel(user), nil
}
userCopy := *user
for _, event := range events {
if err := userCopy.AppendEvent(event); err != nil {
return view_model.UserToModel(user), nil
return user_view_model.UserToModel(user), nil
}
}
return view_model.UserToModel(&userCopy), nil
return user_view_model.UserToModel(&userCopy), nil
}

View File

@@ -13,29 +13,31 @@ import (
"github.com/caos/zitadel/internal/auth_request/repository/cache"
"github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
org_model "github.com/caos/zitadel/internal/org/model"
org_view_model "github.com/caos/zitadel/internal/org/repository/view/model"
user_model "github.com/caos/zitadel/internal/user/model"
user_event "github.com/caos/zitadel/internal/user/repository/eventsourcing"
user_es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
view_model "github.com/caos/zitadel/internal/user/repository/view/model"
user_view_model "github.com/caos/zitadel/internal/user/repository/view/model"
)
type mockViewNoUserSession struct{}
func (m *mockViewNoUserSession) UserSessionByIDs(string, string) (*view_model.UserSessionView, error) {
func (m *mockViewNoUserSession) UserSessionByIDs(string, string) (*user_view_model.UserSessionView, error) {
return nil, errors.ThrowNotFound(nil, "id", "user session not found")
}
func (m *mockViewNoUserSession) UserSessionsByAgentID(string) ([]*view_model.UserSessionView, error) {
func (m *mockViewNoUserSession) UserSessionsByAgentID(string) ([]*user_view_model.UserSessionView, error) {
return nil, errors.ThrowInternal(nil, "id", "internal error")
}
type mockViewErrUserSession struct{}
func (m *mockViewErrUserSession) UserSessionByIDs(string, string) (*view_model.UserSessionView, error) {
func (m *mockViewErrUserSession) UserSessionByIDs(string, string) (*user_view_model.UserSessionView, error) {
return nil, errors.ThrowInternal(nil, "id", "internal error")
}
func (m *mockViewErrUserSession) UserSessionsByAgentID(string) ([]*view_model.UserSessionView, error) {
func (m *mockViewErrUserSession) UserSessionsByAgentID(string) ([]*user_view_model.UserSessionView, error) {
return nil, errors.ThrowInternal(nil, "id", "internal error")
}
@@ -50,17 +52,17 @@ type mockUser struct {
LoginName string
}
func (m *mockViewUserSession) UserSessionByIDs(string, string) (*view_model.UserSessionView, error) {
return &view_model.UserSessionView{
func (m *mockViewUserSession) UserSessionByIDs(string, string) (*user_view_model.UserSessionView, error) {
return &user_view_model.UserSessionView{
PasswordVerification: m.PasswordVerification,
MfaSoftwareVerification: m.MfaSoftwareVerification,
}, nil
}
func (m *mockViewUserSession) UserSessionsByAgentID(string) ([]*view_model.UserSessionView, error) {
sessions := make([]*view_model.UserSessionView, len(m.Users))
func (m *mockViewUserSession) UserSessionsByAgentID(string) ([]*user_view_model.UserSessionView, error) {
sessions := make([]*user_view_model.UserSessionView, len(m.Users))
for i, user := range m.Users {
sessions[i] = &view_model.UserSessionView{
sessions[i] = &user_view_model.UserSessionView{
UserID: user.UserID,
LoginName: user.LoginName,
}
@@ -70,7 +72,7 @@ func (m *mockViewUserSession) UserSessionsByAgentID(string) ([]*view_model.UserS
type mockViewNoUser struct{}
func (m *mockViewNoUser) UserByID(string) (*view_model.UserView, error) {
func (m *mockViewNoUser) UserByID(string) (*user_view_model.UserView, error) {
return nil, errors.ThrowNotFound(nil, "id", "user not found")
}
@@ -102,8 +104,8 @@ type mockViewUser struct {
MfaInitSkipped time.Time
}
func (m *mockViewUser) UserByID(string) (*view_model.UserView, error) {
return &view_model.UserView{
func (m *mockViewUser) UserByID(string) (*user_view_model.UserView, error) {
return &user_view_model.UserView{
InitRequired: m.InitRequired,
PasswordSet: m.PasswordSet,
PasswordChangeRequired: m.PasswordChangeRequired,
@@ -111,9 +113,26 @@ func (m *mockViewUser) UserByID(string) (*view_model.UserView, error) {
OTPState: m.OTPState,
MfaMaxSetUp: m.MfaMaxSetUp,
MfaInitSkipped: m.MfaInitSkipped,
State: int32(user_model.USERSTATE_ACTIVE),
}, nil
}
type mockViewOrg struct {
State org_model.OrgState
}
func (m *mockViewOrg) OrgByID(string) (*org_view_model.OrgView, error) {
return &org_view_model.OrgView{
State: int32(m.State),
}, nil
}
type mockViewErrOrg struct{}
func (m *mockViewErrOrg) OrgByID(string) (*org_view_model.OrgView, error) {
return nil, errors.ThrowInternal(nil, "id", "internal error")
}
func TestAuthRequestRepo_nextSteps(t *testing.T) {
type fields struct {
UserEvents *user_event.UserEventstore
@@ -122,6 +141,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
userSessionViewProvider userSessionViewProvider
userViewProvider userViewProvider
userEventProvider userEventProvider
orgViewProvider orgViewProvider
PasswordCheckLifeTime time.Duration
MfaInitSkippedLifeTime time.Duration
MfaSoftwareCheckLifeTime time.Duration
@@ -203,7 +223,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
nil,
},
{
"user not not found, not found error",
"user not found, not found error",
fields{
userViewProvider: &mockViewNoUser{},
userEventProvider: &mockEventUser{},
@@ -212,6 +232,44 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
nil,
errors.IsNotFound,
},
{
"user not active, precondition failed error",
fields{
userViewProvider: &mockViewUser{},
userEventProvider: &mockEventUser{
&es_models.Event{
AggregateType: user_es_model.UserAggregate,
Type: user_es_model.UserDeactivated,
},
},
orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE},
},
args{&model.AuthRequest{UserID: "UserID"}, false},
nil,
errors.IsPreconditionFailed,
},
{
"org error, internal error",
fields{
userViewProvider: &mockViewUser{},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewErrOrg{},
},
args{&model.AuthRequest{UserID: "UserID"}, false},
nil,
errors.IsInternal,
},
{
"org not active, precondition failed error",
fields{
userViewProvider: &mockViewUser{},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_INACTIVE},
},
args{&model.AuthRequest{UserID: "UserID"}, false},
nil,
errors.IsPreconditionFailed,
},
{
"usersession not found, new user session, password step",
fields{
@@ -220,6 +278,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
PasswordSet: true,
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE},
},
args{&model.AuthRequest{UserID: "UserID"}, false},
[]model.NextStep{&model.PasswordStep{}},
@@ -231,6 +290,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
userSessionViewProvider: &mockViewErrUserSession{},
userViewProvider: &mockViewUser{},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE},
},
args{&model.AuthRequest{UserID: "UserID"}, false},
nil,
@@ -245,6 +305,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
PasswordSet: true,
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE},
},
args{&model.AuthRequest{UserID: "UserID"}, false},
[]model.NextStep{&model.InitUserStep{
@@ -258,6 +319,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
userSessionViewProvider: &mockViewUserSession{},
userViewProvider: &mockViewUser{},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE},
},
args{&model.AuthRequest{UserID: "UserID"}, false},
[]model.NextStep{&model.InitPasswordStep{}},
@@ -271,6 +333,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
PasswordSet: true,
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID"}, false},
@@ -289,6 +352,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
MfaMaxSetUp: int32(model.MfaLevelSoftware),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
},
@@ -312,6 +376,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
MfaMaxSetUp: int32(model.MfaLevelSoftware),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
},
@@ -331,6 +396,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
MfaMaxSetUp: int32(model.MfaLevelSoftware),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
},
@@ -351,6 +417,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
MfaMaxSetUp: int32(model.MfaLevelSoftware),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
},
@@ -371,6 +438,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
MfaMaxSetUp: int32(model.MfaLevelSoftware),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
},
@@ -391,6 +459,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
MfaMaxSetUp: int32(model.MfaLevelSoftware),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.ORGSTATE_ACTIVE},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
},
@@ -408,6 +477,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
UserSessionViewProvider: tt.fields.userSessionViewProvider,
UserViewProvider: tt.fields.userViewProvider,
UserEventProvider: tt.fields.userEventProvider,
OrgViewProvider: tt.fields.orgViewProvider,
PasswordCheckLifeTime: tt.fields.PasswordCheckLifeTime,
MfaInitSkippedLifeTime: tt.fields.MfaInitSkippedLifeTime,
MfaSoftwareCheckLifeTime: tt.fields.MfaSoftwareCheckLifeTime,
@@ -718,6 +788,24 @@ func Test_userSessionByIDs(t *testing.T) {
},
nil,
},
{
"new user events (user deleted), precondition failed error",
args{
userProvider: &mockViewUserSession{
PasswordVerification: time.Now().UTC().Round(1 * time.Second),
},
agentID: "agentID",
user: &user_model.UserView{ID: "id"},
eventProvider: &mockEventUser{
&es_models.Event{
AggregateType: user_es_model.UserAggregate,
Type: user_es_model.UserRemoved,
},
},
},
nil,
errors.IsPreconditionFailed,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -763,6 +851,7 @@ func Test_userByID(t *testing.T) {
},
&user_model.UserView{
PasswordChangeRequired: true,
State: user_model.USERSTATE_ACTIVE,
},
nil,
},
@@ -783,6 +872,7 @@ func Test_userByID(t *testing.T) {
},
&user_model.UserView{
PasswordChangeRequired: true,
State: user_model.USERSTATE_ACTIVE,
},
nil,
},
@@ -807,7 +897,7 @@ func Test_userByID(t *testing.T) {
&user_model.UserView{
PasswordChangeRequired: false,
ChangeDate: time.Now().UTC().Round(1 * time.Second),
State: user_model.USERSTATE_INITIAL,
State: user_model.USERSTATE_ACTIVE,
PasswordChanged: time.Now().UTC().Round(1 * time.Second),
},
nil,