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]>
273 lines
8.6 KiB
Go
273 lines
8.6 KiB
Go
package middleware
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/metadata"
|
|
|
|
"github.com/zitadel/zitadel/internal/api/authz"
|
|
"github.com/zitadel/zitadel/internal/zerrors"
|
|
)
|
|
|
|
const anAPIRole = "AN_API_ROLE"
|
|
|
|
type authzRepoMock struct{}
|
|
|
|
func (v *authzRepoMock) VerifyAccessToken(ctx context.Context, token, clientID, projectID string) (string, string, string, string, string, error) {
|
|
return "", "", "", "", "", nil
|
|
}
|
|
|
|
func (v *authzRepoMock) SearchMyMemberships(ctx context.Context, orgID string, _ bool) ([]*authz.Membership, error) {
|
|
return authz.Memberships{{
|
|
MemberType: authz.MemberTypeOrganization,
|
|
AggregateID: orgID,
|
|
Roles: []string{anAPIRole},
|
|
}}, nil
|
|
}
|
|
|
|
func (v *authzRepoMock) ProjectIDAndOriginsByClientID(ctx context.Context, clientID string) (string, []string, error) {
|
|
return "", nil, nil
|
|
}
|
|
|
|
func (v *authzRepoMock) ExistsOrg(ctx context.Context, orgID, domain string) (string, error) {
|
|
return orgID, nil
|
|
}
|
|
|
|
func (v *authzRepoMock) CheckOrgActive(ctx context.Context, orgID string) error {
|
|
return nil
|
|
}
|
|
|
|
func (v *authzRepoMock) VerifierClientID(ctx context.Context, appName string) (string, string, error) {
|
|
return "", "", nil
|
|
}
|
|
|
|
var (
|
|
accessTokenOK = authz.AccessTokenVerifierFunc(func(ctx context.Context, token string) (userID string, clientID string, agentID string, prefLan string, resourceOwner string, err error) {
|
|
return "user1", "", "", "", "org1", nil
|
|
})
|
|
accessTokenNOK = authz.AccessTokenVerifierFunc(func(ctx context.Context, token string) (userID string, clientID string, agentID string, prefLan string, resourceOwner string, err error) {
|
|
return "", "", "", "", "", zerrors.ThrowUnauthenticated(nil, "TEST-fQHDI", "unauthenticaded")
|
|
})
|
|
systemTokenNOK = authz.SystemTokenVerifierFunc(func(ctx context.Context, token string, orgID string) (memberships authz.Memberships, userID string, err error) {
|
|
return nil, "", errors.New("system token error")
|
|
})
|
|
)
|
|
|
|
func Test_authorize(t *testing.T) {
|
|
type args struct {
|
|
ctx context.Context
|
|
req interface{}
|
|
info *grpc.UnaryServerInfo
|
|
handler grpc.UnaryHandler
|
|
verifier func() authz.APITokenVerifier
|
|
authConfig authz.Config
|
|
}
|
|
type res struct {
|
|
want interface{}
|
|
wantErr bool
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
res res
|
|
}{
|
|
{
|
|
"no token needed ok",
|
|
args{
|
|
ctx: context.Background(),
|
|
req: &mockReq{},
|
|
info: mockInfo("/no/token/needed"),
|
|
handler: emptyMockHandler,
|
|
verifier: func() authz.APITokenVerifier {
|
|
verifier := authz.StartAPITokenVerifier(&authzRepoMock{}, accessTokenOK, systemTokenNOK)
|
|
verifier.RegisterServer("need", "need", authz.MethodMapping{})
|
|
return verifier
|
|
},
|
|
},
|
|
res{
|
|
&mockReq{},
|
|
false,
|
|
},
|
|
},
|
|
{
|
|
"auth header missing error",
|
|
args{
|
|
ctx: context.Background(),
|
|
req: &mockReq{},
|
|
info: mockInfo("/need/authentication"),
|
|
handler: emptyMockHandler,
|
|
verifier: func() authz.APITokenVerifier {
|
|
verifier := authz.StartAPITokenVerifier(&authzRepoMock{}, accessTokenOK, systemTokenNOK)
|
|
verifier.RegisterServer("need", "need", authz.MethodMapping{"/need/authentication": authz.Option{Permission: "authenticated"}})
|
|
return verifier
|
|
},
|
|
authConfig: authz.Config{},
|
|
},
|
|
res{
|
|
nil,
|
|
true,
|
|
},
|
|
},
|
|
{
|
|
"unauthorized error",
|
|
args{
|
|
ctx: metadata.NewIncomingContext(context.Background(), metadata.Pairs("authorization", "wrong")),
|
|
req: &mockReq{},
|
|
info: mockInfo("/need/authentication"),
|
|
handler: emptyMockHandler,
|
|
verifier: func() authz.APITokenVerifier {
|
|
verifier := authz.StartAPITokenVerifier(&authzRepoMock{}, accessTokenOK, systemTokenNOK)
|
|
verifier.RegisterServer("need", "need", authz.MethodMapping{"/need/authentication": authz.Option{Permission: "authenticated"}})
|
|
return verifier
|
|
},
|
|
authConfig: authz.Config{},
|
|
},
|
|
res{
|
|
nil,
|
|
true,
|
|
},
|
|
},
|
|
{
|
|
"authorized ok",
|
|
args{
|
|
ctx: metadata.NewIncomingContext(context.Background(), metadata.Pairs("authorization", "Bearer token")),
|
|
req: &mockReq{},
|
|
info: mockInfo("/need/authentication"),
|
|
handler: emptyMockHandler,
|
|
verifier: func() authz.APITokenVerifier {
|
|
verifier := authz.StartAPITokenVerifier(&authzRepoMock{}, accessTokenOK, systemTokenNOK)
|
|
verifier.RegisterServer("need", "need", authz.MethodMapping{"/need/authentication": authz.Option{Permission: "authenticated"}})
|
|
return verifier
|
|
},
|
|
authConfig: authz.Config{},
|
|
},
|
|
res{
|
|
&mockReq{},
|
|
false,
|
|
},
|
|
},
|
|
{
|
|
"permission denied error",
|
|
args{
|
|
ctx: metadata.NewIncomingContext(context.Background(), metadata.Pairs("authorization", "Bearer token")),
|
|
req: &mockReq{},
|
|
info: mockInfo("/need/authentication"),
|
|
handler: emptyMockHandler,
|
|
verifier: func() authz.APITokenVerifier {
|
|
verifier := authz.StartAPITokenVerifier(&authzRepoMock{}, accessTokenOK, systemTokenNOK)
|
|
verifier.RegisterServer("need", "need", authz.MethodMapping{"/need/authentication": authz.Option{Permission: "to.do.something"}})
|
|
return verifier
|
|
},
|
|
authConfig: authz.Config{
|
|
RolePermissionMappings: []authz.RoleMapping{{
|
|
Role: anAPIRole,
|
|
Permissions: []string{"to.do.something.else"},
|
|
}},
|
|
},
|
|
},
|
|
res{
|
|
nil,
|
|
true,
|
|
},
|
|
},
|
|
{
|
|
"permission ok",
|
|
args{
|
|
ctx: metadata.NewIncomingContext(context.Background(), metadata.Pairs("authorization", "Bearer token")),
|
|
req: &mockReq{},
|
|
info: mockInfo("/need/authentication"),
|
|
handler: emptyMockHandler,
|
|
verifier: func() authz.APITokenVerifier {
|
|
verifier := authz.StartAPITokenVerifier(&authzRepoMock{}, accessTokenOK, systemTokenNOK)
|
|
verifier.RegisterServer("need", "need", authz.MethodMapping{"/need/authentication": authz.Option{Permission: "to.do.something"}})
|
|
return verifier
|
|
},
|
|
authConfig: authz.Config{
|
|
RolePermissionMappings: []authz.RoleMapping{{
|
|
Role: anAPIRole,
|
|
Permissions: []string{"to.do.something"},
|
|
}},
|
|
},
|
|
},
|
|
res{
|
|
&mockReq{},
|
|
false,
|
|
},
|
|
},
|
|
{
|
|
"system token permission denied error",
|
|
args{
|
|
ctx: metadata.NewIncomingContext(context.Background(), metadata.Pairs("authorization", "Bearer token")),
|
|
req: &mockReq{},
|
|
info: mockInfo("/need/authentication"),
|
|
handler: emptyMockHandler,
|
|
verifier: func() authz.APITokenVerifier {
|
|
verifier := authz.StartAPITokenVerifier(&authzRepoMock{}, accessTokenNOK, authz.SystemTokenVerifierFunc(func(ctx context.Context, token string, orgID string) (memberships authz.Memberships, userID string, err error) {
|
|
return authz.Memberships{{
|
|
MemberType: authz.MemberTypeSystem,
|
|
Roles: []string{"A_SYSTEM_ROLE"},
|
|
}}, "systemuser", nil
|
|
}))
|
|
verifier.RegisterServer("need", "need", authz.MethodMapping{"/need/authentication": authz.Option{Permission: "to.do.something"}})
|
|
return verifier
|
|
},
|
|
authConfig: authz.Config{
|
|
RolePermissionMappings: []authz.RoleMapping{{
|
|
Role: "A_SYSTEM_ROLE",
|
|
Permissions: []string{"to.do.something.else"},
|
|
}},
|
|
},
|
|
},
|
|
res{
|
|
nil,
|
|
true,
|
|
},
|
|
},
|
|
{
|
|
"system token permission denied error",
|
|
args{
|
|
ctx: metadata.NewIncomingContext(context.Background(), metadata.Pairs("authorization", "Bearer token")),
|
|
req: &mockReq{},
|
|
info: mockInfo("/need/authentication"),
|
|
handler: emptyMockHandler,
|
|
verifier: func() authz.APITokenVerifier {
|
|
verifier := authz.StartAPITokenVerifier(&authzRepoMock{}, accessTokenNOK, authz.SystemTokenVerifierFunc(func(ctx context.Context, token string, orgID string) (memberships authz.Memberships, userID string, err error) {
|
|
return authz.Memberships{{
|
|
MemberType: authz.MemberTypeSystem,
|
|
Roles: []string{"A_SYSTEM_ROLE"},
|
|
}}, "systemuser", nil
|
|
}))
|
|
verifier.RegisterServer("need", "need", authz.MethodMapping{"/need/authentication": authz.Option{Permission: "to.do.something"}})
|
|
return verifier
|
|
},
|
|
authConfig: authz.Config{
|
|
RolePermissionMappings: []authz.RoleMapping{{
|
|
Role: "A_SYSTEM_ROLE",
|
|
Permissions: []string{"to.do.something"},
|
|
}},
|
|
},
|
|
},
|
|
res{
|
|
&mockReq{},
|
|
false,
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, err := authorize(tt.args.ctx, tt.args.req, tt.args.info, tt.args.handler, tt.args.verifier(), tt.args.authConfig, tt.args.authConfig)
|
|
if (err != nil) != tt.res.wantErr {
|
|
t.Errorf("authorize() error = %v, wantErr %v", err, tt.res.wantErr)
|
|
return
|
|
}
|
|
if !reflect.DeepEqual(got, tt.res.want) {
|
|
t.Errorf("authorize() got = %v, want %v", got, tt.res.want)
|
|
}
|
|
})
|
|
}
|
|
}
|