mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-18 05:52:51 +00:00
* 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]>
121 lines
3.7 KiB
Go
121 lines
3.7 KiB
Go
package query
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"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/eventstore/repository"
|
|
"github.com/zitadel/zitadel/internal/repository/oidcsession"
|
|
"github.com/zitadel/zitadel/internal/repository/org"
|
|
"github.com/zitadel/zitadel/internal/zerrors"
|
|
)
|
|
|
|
func eventFromEventPusherWithCreationDateNow(event eventstore.Command) *repository.Event {
|
|
e := eventFromEventPusher(event)
|
|
e.CreationDate = time.Now()
|
|
return e
|
|
}
|
|
|
|
func TestQueries_ActiveAccessTokenByToken(t *testing.T) {
|
|
type fields struct {
|
|
eventstore func(*testing.T) *eventstore.Eventstore
|
|
}
|
|
type args struct {
|
|
token string
|
|
}
|
|
type res struct {
|
|
wantUserID string
|
|
err error
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
res res
|
|
}{
|
|
{
|
|
name: "invalid token format",
|
|
fields: fields{
|
|
eventstore: expectEventstore(),
|
|
},
|
|
args: args{token: "invalid"},
|
|
res: res{
|
|
err: zerrors.ThrowUnauthenticated(nil, "QUERY-LJK2W", "Errors.OIDCSession.Token.Invalid"),
|
|
},
|
|
},
|
|
{
|
|
name: "active token",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter(
|
|
eventFromEventPusherWithCreationDateNow(
|
|
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
|
"userID", "org1", "sessionID", "clientID", []string{"audience"}, []string{"openid"},
|
|
[]domain.UserAuthMethodType{domain.UserAuthMethodTypePassword}, time.Now(), "nonce", &language.English, nil,
|
|
),
|
|
),
|
|
eventFromEventPusherWithCreationDateNow(
|
|
oidcsession.NewAccessTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
|
"at_accessTokenID", []string{"openid"}, time.Hour, domain.TokenReasonAuthRequest, nil),
|
|
),
|
|
),
|
|
expectFilter(), // no session/user/org termination after token
|
|
),
|
|
},
|
|
args: args{token: "V2_oidcSessionID-at_accessTokenID"},
|
|
res: res{
|
|
wantUserID: "userID",
|
|
},
|
|
},
|
|
{
|
|
name: "org deactivated after token issuance",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter(
|
|
eventFromEventPusherWithCreationDateNow(
|
|
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
|
"userID", "org1", "sessionID", "clientID", []string{"audience"}, []string{"openid"},
|
|
[]domain.UserAuthMethodType{domain.UserAuthMethodTypePassword}, time.Now(), "nonce", &language.English, nil,
|
|
),
|
|
),
|
|
eventFromEventPusherWithCreationDateNow(
|
|
oidcsession.NewAccessTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
|
"at_accessTokenID", []string{"openid"}, time.Hour, domain.TokenReasonAuthRequest, nil),
|
|
),
|
|
),
|
|
expectFilter(
|
|
eventFromEventPusherWithCreationDateNow(
|
|
org.NewOrgDeactivatedEvent(context.Background(), &org.NewAggregate("org1").Aggregate),
|
|
),
|
|
),
|
|
),
|
|
},
|
|
args: args{token: "V2_oidcSessionID-at_accessTokenID"},
|
|
res: res{
|
|
err: zerrors.ThrowUnauthenticated(nil, "QUERY-IJL3H", "Errors.OIDCSession.Token.Invalid"),
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
q := &Queries{
|
|
eventstore: tt.fields.eventstore(t),
|
|
}
|
|
got, err := q.ActiveAccessTokenByToken(context.Background(), tt.args.token)
|
|
require.ErrorIs(t, err, tt.res.err)
|
|
if tt.res.err == nil {
|
|
require.NotNil(t, got)
|
|
assert.Equal(t, tt.res.wantUserID, got.UserID)
|
|
assert.Equal(t, "org1", got.UserResourceOwner)
|
|
}
|
|
})
|
|
}
|
|
}
|