mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-06 06:12:16 +00:00
Merge commit from fork
* fix: block Login V2 auth for users in deactivated organizations Enforce organization state on session creation, OIDC token issuance/refresh, and SAML session creation so deactivated org users cannot authenticate. Co-authored-by: Cursor <[email protected]> * fix: reject claim serving for users in deactivated organizations Require an active resource-owner org in OIDC userinfo and SAML attribute paths, and use distinct error IDs for inactive user vs inactive org at token issuance. Co-authored-by: Cursor <[email protected]> * fix: drop sessions and tokens when an organization is deactivated Mirror OrgRemoved cleanup for OrgDeactivated in the V2 session projection and V1 auth user_session, token, and refresh_token handlers. Also delete V2 sessions on OrgRemoved, which does not emit per-user removal events. Co-authored-by: Cursor <[email protected]> * fix: reject API tokens from deactivated organizations Check the caller's resource-owner org state in authz middleware via a cached OrgByID lookup, so already-issued tokens lose ZITADEL API access when their organization is deactivated. Return unauthenticated (401). Target org remains unrestricted so instance admins can still manage deactivated orgs. Co-authored-by: Cursor <[email protected]> * fix: invalidate OIDC refresh tokens after org deactivation Reject refresh exchange if the user's organization was deactivated after the refresh token was issued, so grants stay dead after reactivate. Co-authored-by: Cursor <[email protected]> * fix: invalidate V2 access tokens after org deactivation Treat OrgDeactivated after the token position as session termination in ActiveAccessTokenByToken, so issued ATs stay dead after reactivation. Co-authored-by: Cursor <[email protected]> --------- Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -1247,6 +1247,7 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
|
||||
false,
|
||||
),
|
||||
),
|
||||
expectFilterActiveOrg("org1"),
|
||||
expectFilter(), // token lifetime
|
||||
expectPush(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
@@ -1345,6 +1346,7 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
|
||||
false,
|
||||
),
|
||||
),
|
||||
expectFilterActiveOrg("org1"),
|
||||
expectFilter(), // token lifetime
|
||||
expectPush(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
@@ -1450,6 +1452,7 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
|
||||
false,
|
||||
),
|
||||
),
|
||||
expectFilterActiveOrg("org1"),
|
||||
expectFilter(), // token lifetime
|
||||
expectPush(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/eventstore/repository/mock"
|
||||
"github.com/zitadel/zitadel/internal/execution/target"
|
||||
"github.com/zitadel/zitadel/internal/feature"
|
||||
"github.com/zitadel/zitadel/internal/repository/org"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
@@ -104,6 +105,15 @@ func expectFilter(events ...eventstore.Event) expect {
|
||||
m.ExpectFilterEvents(events...)
|
||||
}
|
||||
}
|
||||
|
||||
func expectFilterActiveOrg(orgID string) expect {
|
||||
return expectFilter(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
&org.NewAggregate(orgID).Aggregate,
|
||||
"org"),
|
||||
)
|
||||
}
|
||||
|
||||
func expectFilterError(err error) expect {
|
||||
return func(m *mock.MockRepository) {
|
||||
m.ExpectFilterEventsError(err)
|
||||
|
||||
@@ -241,6 +241,9 @@ func (c *Commands) OIDCSessionByRefreshToken(ctx context.Context, refreshToken s
|
||||
if err = writeModel.CheckRefreshToken(refreshTokenID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = c.checkOrgNotDeactivatedAfter(ctx, writeModel.UserResourceOwner, writeModel.RefreshTokenIssuedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModel, nil
|
||||
}
|
||||
|
||||
@@ -294,13 +297,10 @@ func (c *Commands) RevokeOIDCSessionToken(ctx context.Context, token, clientID s
|
||||
}
|
||||
|
||||
func (c *Commands) newOIDCSessionAddEvents(ctx context.Context, userID, resourceOwner string, pending ...eventstore.Command) (*OIDCSessionEvents, error) {
|
||||
userStateModel, err := c.userStateWriteModel(ctx, userID)
|
||||
userStateModel, err := c.userStateForAuthentication(ctx, userID, resourceOwner, "OIDCS-kj3g2", "OIDCS-oR9nA")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !userStateModel.UserState.IsEnabled() {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "OIDCS-kj3g2", "Errors.User.NotActive")
|
||||
}
|
||||
accessTokenLifetime, refreshTokenLifeTime, refreshTokenIdleLifetime, err := c.tokenTokenLifetimes(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -353,12 +353,11 @@ func (c *Commands) newOIDCSessionUpdateEvents(ctx context.Context, refreshToken
|
||||
if err = sessionWriteModel.CheckRefreshToken(refreshTokenID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userStateWriteModel, err := c.userStateWriteModel(ctx, sessionWriteModel.UserID)
|
||||
if err != nil {
|
||||
if err = c.checkOrgNotDeactivatedAfter(ctx, sessionWriteModel.UserResourceOwner, sessionWriteModel.RefreshTokenIssuedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !userStateWriteModel.UserState.IsEnabled() {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "OIDCS-J39h2", "Errors.User.NotActive")
|
||||
if _, err = c.userStateForAuthentication(ctx, sessionWriteModel.UserID, sessionWriteModel.UserResourceOwner, "OIDCS-J39h2", "OIDCS-pQ2mB"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
accessTokenLifetime, refreshTokenLifeTime, refreshTokenIdleLifetime, err := c.tokenTokenLifetimes(ctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -35,6 +35,7 @@ type OIDCSessionWriteModel struct {
|
||||
RefreshToken string
|
||||
RefreshTokenExpiration time.Time
|
||||
RefreshTokenIdleExpiration time.Time
|
||||
RefreshTokenIssuedAt time.Time
|
||||
|
||||
aggregate *eventstore.Aggregate
|
||||
}
|
||||
@@ -125,6 +126,7 @@ func (wm *OIDCSessionWriteModel) reduceRefreshTokenAdded(e *oidcsession.RefreshT
|
||||
wm.RefreshTokenID = e.ID
|
||||
wm.RefreshTokenExpiration = e.CreationDate().Add(e.Lifetime)
|
||||
wm.RefreshTokenIdleExpiration = e.CreationDate().Add(e.IdleLifetime)
|
||||
wm.RefreshTokenIssuedAt = e.CreationDate()
|
||||
}
|
||||
|
||||
func (wm *OIDCSessionWriteModel) reduceRefreshTokenRenewed(e *oidcsession.RefreshTokenRenewedEvent) {
|
||||
@@ -136,6 +138,7 @@ func (wm *OIDCSessionWriteModel) reduceRefreshTokenRevoked(e *oidcsession.Refres
|
||||
wm.RefreshTokenID = ""
|
||||
wm.RefreshTokenExpiration = e.CreationDate()
|
||||
wm.RefreshTokenIdleExpiration = e.CreationDate()
|
||||
wm.RefreshTokenIssuedAt = time.Time{}
|
||||
wm.AccessTokenID = ""
|
||||
wm.AccessTokenExpiration = e.CreationDate()
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/id/mock"
|
||||
"github.com/zitadel/zitadel/internal/repository/authrequest"
|
||||
"github.com/zitadel/zitadel/internal/repository/oidcsession"
|
||||
"github.com/zitadel/zitadel/internal/repository/org"
|
||||
"github.com/zitadel/zitadel/internal/repository/session"
|
||||
"github.com/zitadel/zitadel/internal/repository/sessionlogout"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
@@ -388,6 +389,7 @@ func TestCommands_CreateOIDCSessionFromAuthRequest(t *testing.T) {
|
||||
false,
|
||||
),
|
||||
),
|
||||
expectFilterActiveOrg("org1"),
|
||||
expectFilter(), // token lifetime
|
||||
expectPush(
|
||||
authrequest.NewCodeExchangedEvent(context.Background(), &authrequest.NewAggregate("V2_authRequestID", "instanceID").Aggregate),
|
||||
@@ -523,6 +525,7 @@ func TestCommands_CreateOIDCSessionFromAuthRequest(t *testing.T) {
|
||||
false,
|
||||
),
|
||||
),
|
||||
expectFilterActiveOrg("org1"),
|
||||
expectFilter(), // token lifetime
|
||||
expectPush(
|
||||
authrequest.NewCodeExchangedEvent(context.Background(), &authrequest.NewAggregate("V2_authRequestID", "instanceID").Aggregate),
|
||||
@@ -664,6 +667,7 @@ func TestCommands_CreateOIDCSessionFromAuthRequest(t *testing.T) {
|
||||
false,
|
||||
),
|
||||
),
|
||||
expectFilterActiveOrg("org1"),
|
||||
expectFilter(), // token lifetime
|
||||
expectPush(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
@@ -885,6 +889,7 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
|
||||
false,
|
||||
),
|
||||
),
|
||||
expectFilterActiveOrg("org1"),
|
||||
expectFilter(), // token lifetime
|
||||
expectPush(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
@@ -981,6 +986,7 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
|
||||
false,
|
||||
),
|
||||
),
|
||||
expectFilterActiveOrg("org1"),
|
||||
expectFilter(), // token lifetime
|
||||
expectPush(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
@@ -1062,6 +1068,7 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
|
||||
false,
|
||||
),
|
||||
),
|
||||
expectFilterActiveOrg("org1"),
|
||||
expectFilter(), // token lifetime
|
||||
expectPush(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
@@ -1160,6 +1167,7 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
|
||||
false,
|
||||
),
|
||||
),
|
||||
expectFilterActiveOrg("org1"),
|
||||
expectFilter(), // token lifetime
|
||||
expectPush(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
@@ -1258,6 +1266,7 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
|
||||
false,
|
||||
),
|
||||
),
|
||||
expectFilterActiveOrg("org1"),
|
||||
expectFilter(), // token lifetime
|
||||
expectPush(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
@@ -1354,6 +1363,7 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
|
||||
false,
|
||||
),
|
||||
),
|
||||
expectFilterActiveOrg("org1"),
|
||||
expectFilter(), // token lifetime
|
||||
expectPush(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
@@ -1459,6 +1469,7 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
|
||||
false,
|
||||
),
|
||||
),
|
||||
expectFilterActiveOrg("org1"),
|
||||
expectFilter(), // token lifetime
|
||||
),
|
||||
idGenerator: mock.NewIDGeneratorExpectIDs(t, "oidcSessionID"),
|
||||
@@ -1516,6 +1527,7 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
|
||||
false,
|
||||
),
|
||||
),
|
||||
expectFilterActiveOrg("org1"),
|
||||
expectFilter(), // token lifetime
|
||||
expectPush(
|
||||
user.NewUserImpersonatedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate, "clientID", &domain.TokenActor{
|
||||
@@ -1799,6 +1811,7 @@ func TestCommands_ExchangeOIDCSessionRefreshAndAccessToken(t *testing.T) {
|
||||
"rt_refreshTokenID", 7*24*time.Hour, 24*time.Hour),
|
||||
),
|
||||
),
|
||||
expectFilter(), // no OrgDeactivated after refresh token issuance
|
||||
expectFilter(
|
||||
user.NewHumanAddedEvent(
|
||||
context.Background(),
|
||||
@@ -1835,6 +1848,48 @@ func TestCommands_ExchangeOIDCSessionRefreshAndAccessToken(t *testing.T) {
|
||||
err: zerrors.ThrowPreconditionFailed(nil, "OIDCS-J39h2", "Errors.User.NotActive"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"org deactivated after refresh token issuance",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusherWithCreationDateNow(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"userID", "org1", "sessionID", "clientID", []string{"audience"}, []string{"openid", "profile", "offline_access"},
|
||||
[]domain.UserAuthMethodType{domain.UserAuthMethodTypePassword}, testNow, "nonce", &language.Afrikaans,
|
||||
&domain.UserAgent{FingerprintID: gu.Ptr("browserFP")},
|
||||
),
|
||||
),
|
||||
eventFromEventPusherWithCreationDateNow(
|
||||
oidcsession.NewAccessTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"at_accessTokenID", []string{"openid", "profile", "offline_access"}, time.Hour, domain.TokenReasonAuthRequest, nil),
|
||||
),
|
||||
eventFromEventPusherWithCreationDate(
|
||||
oidcsession.NewRefreshTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"rt_refreshTokenID", 7*24*time.Hour, 24*time.Hour),
|
||||
testNow,
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusherWithCreationDate(
|
||||
org.NewOrgDeactivatedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate),
|
||||
testNow.Add(time.Minute),
|
||||
),
|
||||
),
|
||||
),
|
||||
keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instanceID"),
|
||||
refreshToken: "V2_oidcSessionID-rt_refreshTokenID:userID",
|
||||
scope: []string{"openid", "offline_access"},
|
||||
complianceCheck: mockRefreshTokenComplianceChecker(nil),
|
||||
},
|
||||
res{
|
||||
err: zerrors.ThrowPreconditionFailed(nil, "OIDCS-oR9nR", "Errors.OIDCSession.RefreshTokenInvalid"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"refresh with an invalid client id fails",
|
||||
fields{
|
||||
@@ -1856,6 +1911,7 @@ func TestCommands_ExchangeOIDCSessionRefreshAndAccessToken(t *testing.T) {
|
||||
"rt_refreshTokenID", 7*24*time.Hour, 24*time.Hour),
|
||||
),
|
||||
),
|
||||
expectFilter(), // no OrgDeactivated after refresh token issuance
|
||||
expectFilter(
|
||||
user.NewHumanAddedEvent(
|
||||
context.Background(),
|
||||
@@ -1871,6 +1927,7 @@ func TestCommands_ExchangeOIDCSessionRefreshAndAccessToken(t *testing.T) {
|
||||
false,
|
||||
),
|
||||
),
|
||||
expectFilterActiveOrg("org1"),
|
||||
expectFilter(),
|
||||
),
|
||||
idGenerator: mock.NewIDGeneratorExpectIDs(t),
|
||||
@@ -1908,6 +1965,7 @@ func TestCommands_ExchangeOIDCSessionRefreshAndAccessToken(t *testing.T) {
|
||||
"rt_refreshTokenID", 7*24*time.Hour, 24*time.Hour),
|
||||
),
|
||||
),
|
||||
expectFilter(), // no OrgDeactivated after refresh token issuance
|
||||
expectFilter(
|
||||
user.NewHumanAddedEvent(
|
||||
context.Background(),
|
||||
@@ -1923,6 +1981,7 @@ func TestCommands_ExchangeOIDCSessionRefreshAndAccessToken(t *testing.T) {
|
||||
false,
|
||||
),
|
||||
),
|
||||
expectFilterActiveOrg("org1"),
|
||||
expectFilter(), // token lifetime
|
||||
expectPush(
|
||||
oidcsession.NewAccessTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
@@ -2121,6 +2180,7 @@ func TestCommands_OIDCSessionByRefreshToken(t *testing.T) {
|
||||
"rt_refreshTokenID", 7*24*time.Hour, 24*time.Hour),
|
||||
),
|
||||
),
|
||||
expectFilter(), // no OrgDeactivated after refresh token issuance
|
||||
),
|
||||
keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/org"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
// checkOrgNotDeactivatedAfter returns an invalid refresh token error if the organization
|
||||
// was deactivated after the given time. This keeps refresh grants dead after reactivation.
|
||||
func (c *Commands) checkOrgNotDeactivatedAfter(ctx context.Context, orgID string, after time.Time) (err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
if orgID == "" || after.IsZero() {
|
||||
return nil
|
||||
}
|
||||
model := &orgDeactivatedAfterModel{
|
||||
orgID: orgID,
|
||||
after: after,
|
||||
}
|
||||
if err = c.eventstore.FilterToQueryReducer(ctx, model); err != nil {
|
||||
return zerrors.ThrowPreconditionFailed(err, "OIDCS-oR9nD", "Errors.Internal")
|
||||
}
|
||||
if model.deactivated {
|
||||
return zerrors.ThrowPreconditionFailed(nil, "OIDCS-oR9nR", "Errors.OIDCSession.RefreshTokenInvalid")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type orgDeactivatedAfterModel struct {
|
||||
orgID string
|
||||
after time.Time
|
||||
|
||||
events int
|
||||
deactivated bool
|
||||
}
|
||||
|
||||
func (m *orgDeactivatedAfterModel) Reduce() error {
|
||||
m.deactivated = m.events > 0
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *orgDeactivatedAfterModel) AppendEvents(events ...eventstore.Event) {
|
||||
m.events += len(events)
|
||||
}
|
||||
|
||||
func (m *orgDeactivatedAfterModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
CreationDateAfter(m.after).
|
||||
AddQuery().
|
||||
AggregateTypes(org.AggregateType).
|
||||
AggregateIDs(m.orgID).
|
||||
EventTypes(org.OrgDeactivatedEventType).
|
||||
Builder()
|
||||
}
|
||||
@@ -93,13 +93,10 @@ func (c *Commands) CreateSAMLSessionFromSAMLRequest(ctx context.Context, samlReq
|
||||
}
|
||||
|
||||
func (c *Commands) newSAMLSessionAddEvents(ctx context.Context, userID, resourceOwner string, pending ...eventstore.Command) (*SAMLSessionEvents, error) {
|
||||
userStateModel, err := c.userStateWriteModel(ctx, userID)
|
||||
userStateModel, err := c.userStateForAuthentication(ctx, userID, resourceOwner, "SAML-1768ZQpmcP", "SAML-oR9nA")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !userStateModel.UserState.IsEnabled() {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "SAML-1768ZQpmcP", "Errors.User.NotActive")
|
||||
}
|
||||
sessionID, err := c.idGenerator.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -299,6 +299,7 @@ func TestCommands_CreateSAMLSessionFromSAMLRequest(t *testing.T) {
|
||||
false,
|
||||
),
|
||||
),
|
||||
expectFilterActiveOrg("org1"),
|
||||
expectPush(
|
||||
samlsession.NewAddedEvent(context.Background(), &samlsession.NewAggregate("V2_samlSessionID", "org1").Aggregate,
|
||||
"userID", "org1", "sessionID", "issuer", []string{"issuer"},
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
func (c *Commands) userStateForAuthentication(ctx context.Context, userID, resourceOwner, userErrorID, orgErrorID string) (_ *UserV2WriteModel, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
userStateModel, err := c.userStateWriteModel(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !userStateModel.UserState.IsEnabled() {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, userErrorID, "Errors.User.NotActive")
|
||||
}
|
||||
if err := c.checkOrgActiveForAuthentication(ctx, resourceOwner, orgErrorID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return userStateModel, nil
|
||||
}
|
||||
|
||||
func (c *Commands) checkOrgActiveForAuthentication(ctx context.Context, orgID, errorID string) (err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
orgWriteModel, err := c.getOrgWriteModelByID(ctx, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !orgWriteModel.State.IsActive() {
|
||||
return zerrors.ThrowPreconditionFailed(nil, errorID, "Errors.User.NotActive")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/org"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
func TestCommands_userStateForAuthentication(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
userID string
|
||||
resourceOwner string
|
||||
userErrorID string
|
||||
orgErrorID string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
args args
|
||||
wantErr func(error) bool
|
||||
}{
|
||||
{
|
||||
name: "user not active",
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
user.NewHumanAddedEvent(
|
||||
context.Background(),
|
||||
&user.NewAggregate("userID", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"nickname",
|
||||
"displayname",
|
||||
language.English,
|
||||
domain.GenderUnspecified,
|
||||
"email",
|
||||
false,
|
||||
),
|
||||
user.NewUserDeactivatedEvent(
|
||||
context.Background(),
|
||||
&user.NewAggregate("userID", "org1").Aggregate,
|
||||
),
|
||||
),
|
||||
),
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "userID",
|
||||
resourceOwner: "org1",
|
||||
userErrorID: "TEST-1",
|
||||
orgErrorID: "TEST-1-ORG",
|
||||
},
|
||||
wantErr: zerrors.IsPreconditionFailed,
|
||||
},
|
||||
{
|
||||
name: "org not active",
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
user.NewHumanAddedEvent(
|
||||
context.Background(),
|
||||
&user.NewAggregate("userID", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"nickname",
|
||||
"displayname",
|
||||
language.English,
|
||||
domain.GenderUnspecified,
|
||||
"email",
|
||||
false,
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate,
|
||||
"org"),
|
||||
org.NewOrgDeactivatedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate),
|
||||
),
|
||||
),
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "userID",
|
||||
resourceOwner: "org1",
|
||||
userErrorID: "TEST-2",
|
||||
orgErrorID: "TEST-2-ORG",
|
||||
},
|
||||
wantErr: zerrors.IsPreconditionFailed,
|
||||
},
|
||||
{
|
||||
name: "success",
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
user.NewHumanAddedEvent(
|
||||
context.Background(),
|
||||
&user.NewAggregate("userID", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"nickname",
|
||||
"displayname",
|
||||
language.English,
|
||||
domain.GenderUnspecified,
|
||||
"email",
|
||||
false,
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate,
|
||||
"org"),
|
||||
),
|
||||
),
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "userID",
|
||||
resourceOwner: "org1",
|
||||
userErrorID: "TEST-3",
|
||||
orgErrorID: "TEST-3-ORG",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.eventstore(t),
|
||||
}
|
||||
got, err := c.userStateForAuthentication(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.userErrorID, tt.args.orgErrorID)
|
||||
if tt.wantErr != nil {
|
||||
require.Error(t, err)
|
||||
require.True(t, tt.wantErr(err))
|
||||
require.Nil(t, got)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, got)
|
||||
require.Equal(t, "userID", got.AggregateID)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user