Merge commit from fork

* fix: add `Scopes` to `Request` interface so that scopes can be validated on all requests

* feat: assert org from scope exists when authorizing requests

* fix: check all scopes

* comments

* check org on callback creation

* fix tests

* clarifications

* fix scope marshaling

* fix: enfore organization for authrequest in initiation

* fix: filter sessions on the accounts page by organization scope

* add integration tests

---------

Co-authored-by: Livio Spring <[email protected]>
Co-authored-by: Max Peintner <[email protected]>
This commit is contained in:
Wim Van Laer
2026-03-17 12:54:27 +01:00
committed by GitHub
co-authored by Livio Spring Max Peintner
parent aa4432dc99
commit c3a6bdb0bb
21 changed files with 729 additions and 115 deletions
@@ -15,6 +15,7 @@ import (
"github.com/zitadel/oidc/v3/pkg/oidc"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/integration"
"github.com/zitadel/zitadel/pkg/grpc/app"
filter "github.com/zitadel/zitadel/pkg/grpc/filter/v2beta"
@@ -163,6 +164,25 @@ func TestServer_CreateCallback(t *testing.T) {
},
wantErr: true,
},
{
name: "invalid organization",
ctx: CTXLoginClient,
req: &oidc_pb.CreateCallbackRequest{
AuthRequestId: func() string {
org := Instance.CreateOrganization(IAMCTX, integration.OrganizationName(), integration.Email())
_, authRequestID, err := Instance.CreateOIDCAuthRequest(CTXLoginClient, client.GetClientId(), Instance.Users.Get(integration.UserTypeLogin).ID, redirectURI, oidc.ScopeOpenID, domain.OrgIDScope+org.OrganizationId)
require.NoError(t, err)
return authRequestID
}(),
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
Session: &oidc_pb.Session{
SessionId: sessionResp.GetSessionId(),
SessionToken: sessionResp.GetSessionToken(),
},
},
},
wantErr: true,
},
{
name: "fail callback",
ctx: CTXLoginClient,
@@ -356,6 +376,31 @@ func TestServer_CreateCallback(t *testing.T) {
},
wantErr: false,
},
{
name: "callback with required organization",
ctx: CTXLoginClient,
req: &oidc_pb.CreateCallbackRequest{
AuthRequestId: func() string {
_, authRequestID, err := Instance.CreateOIDCAuthRequest(CTXLoginClient, client.GetClientId(), Instance.Users.Get(integration.UserTypeLogin).ID, redirectURI, oidc.ScopeOpenID, domain.OrgIDScope+Instance.DefaultOrg.Id)
require.NoError(t, err)
return authRequestID
}(),
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
Session: &oidc_pb.Session{
SessionId: sessionResp.GetSessionId(),
SessionToken: sessionResp.GetSessionToken(),
},
},
},
want: &oidc_pb.CreateCallbackResponse{
CallbackUrl: `oidcintegrationtest:\/\/callback\?code=(.*)&state=state`,
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Instance.ID(),
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -749,7 +794,7 @@ func TestServer_AuthorizeOrDenyDeviceAuthorization(t *testing.T) {
ctx: CTXLoginClient,
req: &oidc_pb.AuthorizeOrDenyDeviceAuthorizationRequest{
DeviceAuthorizationId: func() string {
req, err := Instance.CreateDeviceAuthorizationRequest(CTXLoginClient, client.GetClientId(), "openid")
req, err := Instance.CreateDeviceAuthorizationRequest(CTXLoginClient, client.GetClientId(), oidc.ScopeOpenID)
require.NoError(t, err)
var id string
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTXLoginClient, time.Minute)
@@ -776,7 +821,7 @@ func TestServer_AuthorizeOrDenyDeviceAuthorization(t *testing.T) {
ctx: CTXLoginClient,
req: &oidc_pb.AuthorizeOrDenyDeviceAuthorizationRequest{
DeviceAuthorizationId: func() string {
req, err := Instance.CreateDeviceAuthorizationRequest(CTXLoginClient, client.GetClientId(), "openid")
req, err := Instance.CreateDeviceAuthorizationRequest(CTXLoginClient, client.GetClientId(), oidc.ScopeOpenID)
require.NoError(t, err)
var id string
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTXLoginClient, time.Minute)
@@ -798,12 +843,40 @@ func TestServer_AuthorizeOrDenyDeviceAuthorization(t *testing.T) {
},
wantErr: true,
},
{
name: "invalid organization",
ctx: CTXLoginClient,
req: &oidc_pb.AuthorizeOrDenyDeviceAuthorizationRequest{
DeviceAuthorizationId: func() string {
org := Instance.CreateOrganization(IAMCTX, integration.OrganizationName(), integration.Email())
req, err := Instance.CreateDeviceAuthorizationRequest(CTXLoginClient, client.GetClientId(), oidc.ScopeOpenID, domain.OrgIDScope+org.OrganizationId)
require.NoError(t, err)
var id string
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTXLoginClient, time.Minute)
require.EventuallyWithT(t, func(collectT *assert.CollectT) {
resp, err := Instance.Client.OIDCv2.GetDeviceAuthorizationRequest(CTXLoginClient, &oidc_pb.GetDeviceAuthorizationRequestRequest{
UserCode: req.UserCode,
})
assert.NoError(collectT, err)
id = resp.GetDeviceAuthorizationRequest().GetId()
}, retryDuration, tick)
return id
}(),
Decision: &oidc_pb.AuthorizeOrDenyDeviceAuthorizationRequest_Session{
Session: &oidc_pb.Session{
SessionId: sessionResp.GetSessionId(),
SessionToken: sessionResp.GetSessionToken(),
},
},
},
wantErr: true,
},
{
name: "deny device authorization",
ctx: CTXLoginClient,
req: &oidc_pb.AuthorizeOrDenyDeviceAuthorizationRequest{
DeviceAuthorizationId: func() string {
req, err := Instance.CreateDeviceAuthorizationRequest(CTXLoginClient, client.GetClientId(), "openid")
req, err := Instance.CreateDeviceAuthorizationRequest(CTXLoginClient, client.GetClientId(), oidc.ScopeOpenID)
require.NoError(t, err)
var id string
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTXLoginClient, time.Minute)
@@ -826,7 +899,7 @@ func TestServer_AuthorizeOrDenyDeviceAuthorization(t *testing.T) {
ctx: CTX,
req: &oidc_pb.AuthorizeOrDenyDeviceAuthorizationRequest{
DeviceAuthorizationId: func() string {
req, err := Instance.CreateDeviceAuthorizationRequest(CTXLoginClient, client.GetClientId(), "openid")
req, err := Instance.CreateDeviceAuthorizationRequest(CTXLoginClient, client.GetClientId(), oidc.ScopeOpenID)
require.NoError(t, err)
var id string
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTXLoginClient, time.Minute)
@@ -849,7 +922,7 @@ func TestServer_AuthorizeOrDenyDeviceAuthorization(t *testing.T) {
ctx: CTX,
req: &oidc_pb.AuthorizeOrDenyDeviceAuthorizationRequest{
DeviceAuthorizationId: func() string {
req, err := Instance.CreateDeviceAuthorizationRequest(CTXLoginClient, client.GetClientId(), "openid")
req, err := Instance.CreateDeviceAuthorizationRequest(CTXLoginClient, client.GetClientId(), oidc.ScopeOpenID)
require.NoError(t, err)
var id string
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTXLoginClient, time.Minute)
@@ -876,7 +949,34 @@ func TestServer_AuthorizeOrDenyDeviceAuthorization(t *testing.T) {
ctx: CTXLoginClient,
req: &oidc_pb.AuthorizeOrDenyDeviceAuthorizationRequest{
DeviceAuthorizationId: func() string {
req, err := Instance.CreateDeviceAuthorizationRequest(CTXLoginClient, client.GetClientId(), "openid")
req, err := Instance.CreateDeviceAuthorizationRequest(CTXLoginClient, client.GetClientId(), oidc.ScopeOpenID)
require.NoError(t, err)
var id string
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTXLoginClient, time.Minute)
require.EventuallyWithT(t, func(collectT *assert.CollectT) {
resp, err := Instance.Client.OIDCv2.GetDeviceAuthorizationRequest(CTXLoginClient, &oidc_pb.GetDeviceAuthorizationRequestRequest{
UserCode: req.UserCode,
})
assert.NoError(collectT, err)
id = resp.GetDeviceAuthorizationRequest().GetId()
}, retryDuration, tick)
return id
}(),
Decision: &oidc_pb.AuthorizeOrDenyDeviceAuthorizationRequest_Session{
Session: &oidc_pb.Session{
SessionId: sessionResp.GetSessionId(),
SessionToken: sessionResp.GetSessionToken(),
},
},
},
wantErr: false,
},
{
name: "authorize, with organization scope",
ctx: CTXLoginClient,
req: &oidc_pb.AuthorizeOrDenyDeviceAuthorizationRequest{
DeviceAuthorizationId: func() string {
req, err := Instance.CreateDeviceAuthorizationRequest(CTXLoginClient, client.GetClientId(), oidc.ScopeOpenID, domain.OrgIDScope+Instance.DefaultOrg.GetId())
require.NoError(t, err)
var id string
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTXLoginClient, time.Minute)