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
+19 -4
View File
@@ -17,12 +17,27 @@ export async function generateMetadata(): Promise<Metadata> {
return { title: t("title") };
}
async function loadSessions({ serviceConfig }: { serviceConfig: ServiceConfig }) {
async function loadSessions({
serviceConfig,
organization,
}: {
serviceConfig: ServiceConfig;
organization?: string;
}) {
const cookieIds = await getAllSessionCookieIds();
if (cookieIds && cookieIds.length) {
const response = await listSessions({ serviceConfig, ids: cookieIds.filter((id) => !!id) as string[] });
return response?.sessions ?? [];
const response = await listSessions({
serviceConfig,
ids: cookieIds.filter((id) => !!id) as string[],
});
let sessions = response?.sessions ?? [];
if (organization) {
sessions = sessions.filter((s) => s.factors?.user?.organizationId === organization);
}
return sessions;
} else {
console.info("No session cookie found.");
return [];
@@ -46,7 +61,7 @@ export default async function Page(props: { searchParams: Promise<Record<string
}
}
let sessions = await loadSessions({ serviceConfig });
let sessions = await loadSessions({ serviceConfig, organization });
const branding = await getBrandingSettings({ serviceConfig, organization: organization ?? defaultOrganization });
+2 -2
View File
@@ -268,7 +268,7 @@ export async function handleOIDCFlowInitiation(params: FlowInitiationParams): Pr
} else if (authRequest.prompt.includes(Prompt.NONE)) {
const securitySettings = await getSecuritySettings({ serviceConfig });
const selectedSession = await findValidSession({ serviceConfig, sessions, authRequest });
const selectedSession = await findValidSession({ serviceConfig, sessions, authRequest, organization });
const noSessionResponse = NextResponse.json({ error: "No active session found" }, { status: 400 });
setCSPHeaders(noSessionResponse, serviceConfig, securitySettings);
@@ -304,7 +304,7 @@ export async function handleOIDCFlowInitiation(params: FlowInitiationParams): Pr
return callbackResponse;
} else {
let selectedSession = await findValidSession({ serviceConfig, sessions, authRequest });
let selectedSession = await findValidSession({ serviceConfig, sessions, authRequest, organization });
if (!selectedSession || !selectedSession.id) {
return gotoAccounts({
+7 -1
View File
@@ -142,13 +142,15 @@ export async function findValidSession({
sessions,
authRequest,
samlRequest,
organization,
}: {
serviceConfig: ServiceConfig;
sessions: Session[];
authRequest?: AuthRequest;
samlRequest?: SAMLRequest;
organization?: string;
}): Promise<Session | undefined> {
const sessionsWithHint = sessions.filter((s) => {
let sessionsWithHint = sessions.filter((s) => {
if (authRequest && authRequest.hintUserId) {
return s.factors?.user?.id === authRequest.hintUserId;
}
@@ -163,6 +165,10 @@ export async function findValidSession({
return true;
});
if (organization) {
sessionsWithHint = sessionsWithHint.filter((s) => s.factors?.user?.organizationId === organization);
}
if (sessionsWithHint.length === 0) {
return undefined;
}
@@ -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)
+48 -7
View File
@@ -80,25 +80,31 @@ func (o *OPStorage) CreateAuthRequest(ctx context.Context, req *oidc.AuthRequest
}
}
func (o *OPStorage) createAuthRequestScopeAndAudience(ctx context.Context, clientID string, reqScope []string) (scope, audience []string, err error) {
func (o *OPStorage) createAuthRequestScopeAndAudience(ctx context.Context, clientID string, reqScope []string) (scope, audience []string, orgID string, err error) {
project, err := o.query.ProjectByClientID(ctx, clientID)
if err != nil {
return nil, nil, err
return nil, nil, "", err
}
orgID, err = o.assertOrgScope(ctx, reqScope)
if err != nil {
return nil, nil, "", err
}
scope, err = o.assertProjectRoleScopesByProject(ctx, project, reqScope)
if err != nil {
return nil, nil, err
return nil, nil, "", err
}
audience, err = o.audienceFromProjectID(ctx, project.ID)
audience = domain.AddAudScopeToAudience(ctx, audience, scope)
if err != nil {
return nil, nil, err
return nil, nil, "", err
}
return scope, audience, nil
return scope, audience, orgID, nil
}
func (o *OPStorage) createAuthRequestLoginClient(ctx context.Context, req *oidc.AuthRequest, hintUserID, loginClient string) (op.AuthRequest, error) {
scope, audience, err := o.createAuthRequestScopeAndAudience(ctx, req.ClientID, req.Scopes)
scope, audience, orgID, err := o.createAuthRequestScopeAndAudience(ctx, req.ClientID, req.Scopes)
if err != nil {
return nil, err
}
@@ -118,6 +124,7 @@ func (o *OPStorage) createAuthRequestLoginClient(ctx context.Context, req *oidc.
UILocales: UILocalesToBusiness(req.UILocales),
MaxAge: MaxAgeToBusiness(req.MaxAge),
Issuer: o.contextToIssuer(ctx),
OrganizationID: orgID,
}
if req.LoginHint != "" {
authRequest.LoginHint = &req.LoginHint
@@ -138,7 +145,8 @@ func (o *OPStorage) createAuthRequest(ctx context.Context, req *oidc.AuthRequest
if !ok {
return nil, zerrors.ThrowPreconditionFailed(nil, "OIDC-sd436", "no user agent id")
}
scope, audience, err := o.createAuthRequestScopeAndAudience(ctx, req.ClientID, req.Scopes)
// we do not need to handle the orgID for the v1 login, since it handles it already
scope, audience, _, err := o.createAuthRequestScopeAndAudience(ctx, req.ClientID, req.Scopes)
if err != nil {
return nil, err
}
@@ -530,6 +538,39 @@ func (o *OPStorage) GetRefreshTokenInfo(ctx context.Context, clientID string, to
return refreshToken.UserID, refreshToken.ID, nil
}
// assertOrgScope checks the scopes for organization scopes and returns the orgID if exactly one org scope is found.
// If multiple org scopes are found or if the org scope is invalid, an error is returned.
// For backwards compatibility, we support both orgID and orgDomain scopes, but they need to be consistent,
// meaning that if both are provided, they need to belong to the same organization.
func (o *OPStorage) assertOrgScope(ctx context.Context, scopes []string) (string, error) {
var id string
for _, scope := range scopes {
if orgID, ok := strings.CutPrefix(scope, domain.OrgIDScope); ok {
org, err := o.query.OrgByID(ctx, orgID)
if err != nil {
return "", err
}
if id != "" && id != org.ID {
return "", oidc.ErrInvalidScope().WithDescription("Only one organization scope may be provided")
}
id = org.ID
continue
}
if orgDomain, ok := strings.CutPrefix(scope, domain.OrgDomainPrimaryScope); ok {
org, err := o.query.OrgByPrimaryDomain(ctx, orgDomain)
if err != nil {
return "", err
}
if id != "" && id != org.ID {
return "", oidc.ErrInvalidScope().WithDescription("Only one organization scope may be provided")
}
id = org.ID
}
}
return id, nil
}
func (o *OPStorage) assertProjectRoleScopesByProject(ctx context.Context, project *query.Project, scopes []string) ([]string, error) {
for _, scope := range scopes {
if strings.HasPrefix(scope, ScopeProjectRolePrefix) {
+7 -7
View File
@@ -119,13 +119,13 @@ func CreateAuthRequestToBusiness(ctx context.Context, authReq *oidc.AuthRequest,
UserID: userID,
InstanceID: authz.GetInstance(ctx).InstanceID(),
Audience: audience,
Request: &domain.AuthRequestOIDC{
Scopes: authReq.Scopes,
ResponseType: ResponseTypeToBusiness(authReq.ResponseType),
ResponseMode: ResponseModeToBusiness(authReq.ResponseMode),
Nonce: authReq.Nonce,
CodeChallenge: CodeChallengeToBusiness(authReq.CodeChallenge, authReq.CodeChallengeMethod),
},
Request: domain.NewAuthRequestOIDC(
authReq.Scopes,
ResponseTypeToBusiness(authReq.ResponseType),
ResponseModeToBusiness(authReq.ResponseMode),
authReq.Nonce,
CodeChallengeToBusiness(authReq.CodeChallenge, authReq.CodeChallengeMethod),
),
}
}
+2 -2
View File
@@ -76,11 +76,11 @@ func (o *OPStorage) StoreDeviceAuthorization(ctx context.Context, clientID, devi
logger.OnError(err).Error(logMsg)
span.EndWithError(err)
}()
scope, audience, err := o.createAuthRequestScopeAndAudience(ctx, clientID, scope)
scope, audience, orgID, err := o.createAuthRequestScopeAndAudience(ctx, clientID, scope)
if err != nil {
return err
}
details, err := o.command.AddDeviceAuth(ctx, clientID, deviceCode, userCode, expires, scope, audience, slices.Contains(scope, oidc.ScopeOfflineAccess))
details, err := o.command.AddDeviceAuth(ctx, clientID, deviceCode, userCode, orgID, expires, scope, audience, slices.Contains(scope, oidc.ScopeOfflineAccess))
if err == nil {
logger.SetFields("details", details).Debug(logMsg)
}
@@ -17,6 +17,7 @@ import (
http_utils "github.com/zitadel/zitadel/internal/api/http"
oidc_api "github.com/zitadel/zitadel/internal/api/oidc"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/integration"
oidc_pb "github.com/zitadel/zitadel/pkg/grpc/oidc/v2"
"github.com/zitadel/zitadel/pkg/grpc/session/v2"
@@ -36,6 +37,20 @@ func TestOPStorage_CreateAuthRequest(t *testing.T) {
id2 := createAuthRequestNoLoginClientHeader(t, Instance, clientIDV2, redirectURI)
require.Contains(t, id2, command.IDPrefixV2)
// valid org scope must succeed
_, _, err := Instance.CreateOIDCAuthRequest(CTX, clientID, Instance.Users.Get(integration.UserTypeLogin).ID, redirectURI, oidc.ScopeOpenID, domain.OrgIDScope+Instance.DefaultOrg.Id)
require.NoError(t, err)
_, _, err = Instance.CreateOIDCAuthRequest(CTX, clientID, Instance.Users.Get(integration.UserTypeLogin).ID, redirectURI, oidc.ScopeOpenID, domain.OrgIDScope+Instance.DefaultOrg.Id)
require.NoError(t, err)
// invalid org scope must fail
_, _, err = Instance.CreateOIDCAuthRequest(CTX, clientID, Instance.Users.Get(integration.UserTypeLogin).ID, redirectURI, oidc.ScopeOpenID, domain.OrgIDScope+"invalid")
require.Error(t, err)
_, _, err = Instance.CreateOIDCAuthRequest(CTX, clientID, Instance.Users.Get(integration.UserTypeLogin).ID, redirectURI, oidc.ScopeOpenID, domain.OrgIDScope+"invalid")
require.Error(t, err)
}
func TestOPStorage_CreateAccessToken_code(t *testing.T) {
+23 -17
View File
@@ -30,6 +30,7 @@ type AuthRequest struct {
HintUserID *string
NeedRefreshToken bool
Issuer string
OrganizationID string
}
type CurrentAuthRequest struct {
@@ -75,6 +76,7 @@ func (c *Commands) AddAuthRequest(ctx context.Context, authRequest *AuthRequest)
authRequest.HintUserID,
authRequest.NeedRefreshToken,
authRequest.Issuer,
authRequest.OrganizationID,
))
if err != nil {
return nil, err
@@ -116,6 +118,9 @@ func (c *Commands) LinkSessionToAuthRequest(ctx context.Context, id, sessionID,
return nil, nil, err
}
}
if writeModel.OrganizationID != "" && writeModel.OrganizationID != sessionWriteModel.UserResourceOwner {
return nil, nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-59ljd", "Errors.User.NotAllowedOrg")
}
if err := c.pushAppendAndReduce(ctx, writeModel, authrequest.NewSessionLinkedEvent(
ctx, &authrequest.NewAggregate(id, authz.GetInstance(ctx).InstanceID()).Aggregate,
@@ -171,23 +176,24 @@ func (c *Commands) AddAuthRequestCode(ctx context.Context, authRequestID, code s
func authRequestWriteModelToCurrentAuthRequest(writeModel *AuthRequestWriteModel) (_ *CurrentAuthRequest) {
return &CurrentAuthRequest{
AuthRequest: &AuthRequest{
ID: writeModel.AggregateID,
LoginClient: writeModel.LoginClient,
ClientID: writeModel.ClientID,
RedirectURI: writeModel.RedirectURI,
State: writeModel.State,
Nonce: writeModel.Nonce,
Scope: writeModel.Scope,
Audience: writeModel.Audience,
ResponseType: writeModel.ResponseType,
ResponseMode: writeModel.ResponseMode,
CodeChallenge: writeModel.CodeChallenge,
Prompt: writeModel.Prompt,
UILocales: writeModel.UILocales,
MaxAge: writeModel.MaxAge,
LoginHint: writeModel.LoginHint,
HintUserID: writeModel.HintUserID,
Issuer: writeModel.Issuer,
ID: writeModel.AggregateID,
LoginClient: writeModel.LoginClient,
ClientID: writeModel.ClientID,
RedirectURI: writeModel.RedirectURI,
State: writeModel.State,
Nonce: writeModel.Nonce,
Scope: writeModel.Scope,
Audience: writeModel.Audience,
ResponseType: writeModel.ResponseType,
ResponseMode: writeModel.ResponseMode,
CodeChallenge: writeModel.CodeChallenge,
Prompt: writeModel.Prompt,
UILocales: writeModel.UILocales,
MaxAge: writeModel.MaxAge,
LoginHint: writeModel.LoginHint,
HintUserID: writeModel.HintUserID,
Issuer: writeModel.Issuer,
OrganizationID: writeModel.OrganizationID,
},
SessionID: writeModel.SessionID,
UserID: writeModel.UserID,
+2
View File
@@ -37,6 +37,7 @@ type AuthRequestWriteModel struct {
AuthRequestState domain.AuthRequestState
NeedRefreshToken bool
Issuer string
OrganizationID string
}
func NewAuthRequestWriteModel(ctx context.Context, id string) *AuthRequestWriteModel {
@@ -70,6 +71,7 @@ func (m *AuthRequestWriteModel) Reduce() error {
m.AuthRequestState = domain.AuthRequestStateAdded
m.NeedRefreshToken = e.NeedRefreshToken
m.Issuer = e.Issuer
m.OrganizationID = e.OrganizationID
case *authrequest.SessionLinkedEvent:
m.SessionID = e.SessionID
m.UserID = e.UserID
+187 -12
View File
@@ -63,6 +63,7 @@ func TestCommands_AddAuthRequest(t *testing.T) {
nil,
false,
"issuer",
"",
),
),
),
@@ -103,6 +104,7 @@ func TestCommands_AddAuthRequest(t *testing.T) {
gu.Ptr("hintUserID"),
false,
"issuer",
"organizationID",
),
),
),
@@ -124,12 +126,13 @@ func TestCommands_AddAuthRequest(t *testing.T) {
Challenge: "challenge",
Method: domain.CodeChallengeMethodS256,
},
Prompt: []domain.Prompt{domain.PromptNone},
UILocales: []string{"en", "de"},
MaxAge: gu.Ptr(time.Duration(0)),
LoginHint: gu.Ptr("loginHint"),
HintUserID: gu.Ptr("hintUserID"),
Issuer: "issuer",
Prompt: []domain.Prompt{domain.PromptNone},
UILocales: []string{"en", "de"},
MaxAge: gu.Ptr(time.Duration(0)),
LoginHint: gu.Ptr("loginHint"),
HintUserID: gu.Ptr("hintUserID"),
Issuer: "issuer",
OrganizationID: "organizationID",
},
},
&CurrentAuthRequest{
@@ -148,12 +151,13 @@ func TestCommands_AddAuthRequest(t *testing.T) {
Challenge: "challenge",
Method: domain.CodeChallengeMethodS256,
},
Prompt: []domain.Prompt{domain.PromptNone},
UILocales: []string{"en", "de"},
MaxAge: gu.Ptr(time.Duration(0)),
LoginHint: gu.Ptr("loginHint"),
HintUserID: gu.Ptr("hintUserID"),
Issuer: "issuer",
Prompt: []domain.Prompt{domain.PromptNone},
UILocales: []string{"en", "de"},
MaxAge: gu.Ptr(time.Duration(0)),
LoginHint: gu.Ptr("loginHint"),
HintUserID: gu.Ptr("hintUserID"),
Issuer: "issuer",
OrganizationID: "organizationID",
},
},
nil,
@@ -239,6 +243,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
nil,
true,
"issuer",
"",
),
),
eventFromEventPusher(
@@ -282,6 +287,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
nil,
true,
"issuer",
"",
),
),
),
@@ -324,6 +330,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
nil,
true,
"issuer",
"",
),
),
),
@@ -364,6 +371,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
nil,
true,
"issuer",
"",
),
),
),
@@ -427,6 +435,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
nil,
true,
"issuer",
"",
),
),
),
@@ -455,6 +464,71 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
wantErr: zerrors.ThrowPermissionDenied(nil, "COMMAND-sGr42", "Errors.Session.Token.Invalid"),
},
},
{
"invalid organization",
fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_id", "instanceID").Aggregate,
"loginClient",
"clientID",
"redirectURI",
"state",
"nonce",
[]string{"openid"},
[]string{"audience"},
domain.OIDCResponseTypeCode,
domain.OIDCResponseModeQuery,
nil,
nil,
nil,
nil,
nil,
nil,
true,
"issuer",
"organizationID",
),
),
),
expectFilter(
eventFromEventPusher(
session.NewAddedEvent(mockCtx,
&session.NewAggregate("sessionID", "instance1").Aggregate,
&domain.UserAgent{
FingerprintID: gu.Ptr("fp1"),
IP: net.ParseIP("1.2.3.4"),
Description: gu.Ptr("firefox"),
Header: http.Header{"foo": []string{"bar"}},
},
)),
eventFromEventPusher(
session.NewUserCheckedEvent(mockCtx, &session.NewAggregate("sessionID", "instance1").Aggregate,
"userID", "org1", testNow, &language.Afrikaans),
),
eventFromEventPusher(
session.NewPasswordCheckedEvent(mockCtx, &session.NewAggregate("sessionID", "instance1").Aggregate,
testNow),
),
eventFromEventPusherWithCreationDateNow(
session.NewLifetimeSetEvent(mockCtx, &session.NewAggregate("sessionID", "instance1").Aggregate,
2*time.Minute),
),
),
),
tokenVerifier: newMockTokenVerifierValid(),
},
args{
ctx: mockCtx,
id: "V2_id",
sessionID: "sessionID",
sessionToken: "token",
},
res{
wantErr: zerrors.ThrowPreconditionFailed(nil, "COMMAND-59ljd", "Errors.User.NotAllowedOrg"),
},
},
{
"linked",
fields{
@@ -479,6 +553,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
nil,
true,
"issuer",
"",
),
),
),
@@ -545,6 +620,98 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
},
},
},
{
"linked with organization check",
fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_id", "instanceID").Aggregate,
"loginClient",
"clientID",
"redirectURI",
"state",
"nonce",
[]string{"openid"},
[]string{"audience"},
domain.OIDCResponseTypeCode,
domain.OIDCResponseModeQuery,
nil,
nil,
nil,
nil,
nil,
nil,
true,
"issuer",
"org1",
),
),
),
expectFilter(
eventFromEventPusher(
session.NewAddedEvent(mockCtx,
&session.NewAggregate("sessionID", "instance1").Aggregate,
&domain.UserAgent{
FingerprintID: gu.Ptr("fp1"),
IP: net.ParseIP("1.2.3.4"),
Description: gu.Ptr("firefox"),
Header: http.Header{"foo": []string{"bar"}},
},
)),
eventFromEventPusher(
session.NewUserCheckedEvent(mockCtx, &session.NewAggregate("sessionID", "instance1").Aggregate,
"userID", "org1", testNow, &language.Afrikaans),
),
eventFromEventPusher(
session.NewPasswordCheckedEvent(mockCtx, &session.NewAggregate("sessionID", "instance1").Aggregate,
testNow),
),
eventFromEventPusherWithCreationDateNow(
session.NewLifetimeSetEvent(mockCtx, &session.NewAggregate("sessionID", "instance1").Aggregate,
2*time.Minute),
),
),
expectPush(
authrequest.NewSessionLinkedEvent(mockCtx, &authrequest.NewAggregate("V2_id", "instanceID").Aggregate,
"sessionID",
"userID",
testNow,
[]domain.UserAuthMethodType{domain.UserAuthMethodTypePassword},
),
),
),
tokenVerifier: newMockTokenVerifierValid(),
},
args{
ctx: mockCtx,
id: "V2_id",
sessionID: "sessionID",
sessionToken: "token",
},
res{
details: &domain.ObjectDetails{ResourceOwner: "instanceID"},
authReq: &CurrentAuthRequest{
AuthRequest: &AuthRequest{
ID: "V2_id",
LoginClient: "loginClient",
ClientID: "clientID",
RedirectURI: "redirectURI",
State: "state",
Nonce: "nonce",
Scope: []string{"openid"},
Audience: []string{"audience"},
ResponseType: domain.OIDCResponseTypeCode,
ResponseMode: domain.OIDCResponseModeQuery,
Issuer: "issuer",
OrganizationID: "org1",
},
SessionID: "sessionID",
UserID: "userID",
AuthMethods: []domain.UserAuthMethodType{domain.UserAuthMethodTypePassword},
},
},
},
{
"linked with login client check",
fields{
@@ -569,6 +736,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
nil,
true,
"issuer",
"",
),
),
),
@@ -660,6 +828,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
nil,
true,
"issuer",
"",
),
),
),
@@ -752,6 +921,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
nil,
true,
"issuer",
"",
),
),
),
@@ -845,6 +1015,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
nil,
true,
"issuer",
"",
),
),
),
@@ -970,6 +1141,7 @@ func TestCommands_FailAuthRequest(t *testing.T) {
nil,
true,
"issuer",
"",
),
),
),
@@ -1009,6 +1181,7 @@ func TestCommands_FailAuthRequest(t *testing.T) {
nil,
true,
"issuer",
"",
),
),
),
@@ -1113,6 +1286,7 @@ func TestCommands_AddAuthRequestCode(t *testing.T) {
gu.Ptr("hintUserID"),
true,
"issuer",
"",
),
),
),
@@ -1152,6 +1326,7 @@ func TestCommands_AddAuthRequestCode(t *testing.T) {
gu.Ptr("hintUserID"),
true,
"issuer",
"",
),
),
eventFromEventPusher(
+8 -1
View File
@@ -15,7 +15,7 @@ import (
"github.com/zitadel/zitadel/internal/zerrors"
)
func (c *Commands) AddDeviceAuth(ctx context.Context, clientID, deviceCode, userCode string, expires time.Time, scopes, audience []string, needRefreshToken bool) (*domain.ObjectDetails, error) {
func (c *Commands) AddDeviceAuth(ctx context.Context, clientID, deviceCode, userCode, orgID string, expires time.Time, scopes, audience []string, needRefreshToken bool) (*domain.ObjectDetails, error) {
aggr := deviceauth.NewAggregate(deviceCode, authz.GetInstance(ctx).InstanceID())
model := NewDeviceAuthWriteModel(deviceCode, aggr.ResourceOwner)
@@ -29,6 +29,7 @@ func (c *Commands) AddDeviceAuth(ctx context.Context, clientID, deviceCode, user
scopes,
audience,
needRefreshToken,
orgID,
))
if err != nil {
return nil, err
@@ -62,6 +63,9 @@ func (c *Commands) ApproveDeviceAuth(
if model.State != domain.DeviceAuthStateInitiated {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-GEJL3", "Errors.DeviceAuth.AlreadyHandled")
}
if model.OrganizationID != "" && model.OrganizationID != userOrgID {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-3tgws", "Errors.User.NotAllowedOrg")
}
pushedEvents, err := c.eventstore.Push(ctx, deviceauth.NewApprovedEvent(ctx, model.aggregate, userID, userOrgID, authMethods, authTime, preferredLanguage, userAgent, sessionID))
if err != nil {
return nil, err
@@ -105,6 +109,9 @@ func (c *Commands) ApproveDeviceAuthWithSession(
if err := c.sessionTokenVerifier(ctx, sessionToken, sessionWriteModel.AggregateID, sessionWriteModel.TokenID); err != nil {
return nil, err
}
if model.OrganizationID != "" && model.OrganizationID != sessionWriteModel.UserResourceOwner {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-D2j34", "Errors.User.NotAllowedOrg")
}
pushedEvents, err := c.eventstore.Push(ctx, deviceauth.NewApprovedEvent(
ctx,
+2
View File
@@ -29,6 +29,7 @@ type DeviceAuthWriteModel struct {
UserAgent *domain.UserAgent
NeedRefreshToken bool
SessionID string
OrganizationID string
}
func NewDeviceAuthWriteModel(deviceCode, resourceOwner string) *DeviceAuthWriteModel {
@@ -53,6 +54,7 @@ func (m *DeviceAuthWriteModel) Reduce() error {
m.Audience = e.Audience
m.State = e.State
m.NeedRefreshToken = e.NeedRefreshToken
m.OrganizationID = e.OrganizationID
case *deviceauth.ApprovedEvent:
m.State = domain.DeviceAuthStateApproved
m.UserID = e.UserID
+227 -24
View File
@@ -49,6 +49,7 @@ func TestCommands_AddDeviceAuth(t *testing.T) {
scopes []string
audience []string
needRefreshToken bool
organizationID string
}
tests := []struct {
name string
@@ -66,7 +67,7 @@ func TestCommands_AddDeviceAuth(t *testing.T) {
deviceauth.NewAggregate("123", "instance1"),
"client_id", "123", "456", now,
[]string{"a", "b", "c"},
[]string{"projectID", "clientID"}, true,
[]string{"projectID", "clientID"}, true, "orgID",
),
)),
},
@@ -79,6 +80,7 @@ func TestCommands_AddDeviceAuth(t *testing.T) {
scopes: []string{"a", "b", "c"},
audience: []string{"projectID", "clientID"},
needRefreshToken: true,
organizationID: "orgID",
},
wantDetails: &domain.ObjectDetails{
ResourceOwner: "instance1",
@@ -93,7 +95,7 @@ func TestCommands_AddDeviceAuth(t *testing.T) {
deviceauth.NewAggregate("123", "instance1"),
"client_id", "123", "456", now,
[]string{"a", "b", "c"},
[]string{"projectID", "clientID"}, false,
[]string{"projectID", "clientID"}, false, "orgID",
)),
),
},
@@ -106,6 +108,7 @@ func TestCommands_AddDeviceAuth(t *testing.T) {
scopes: []string{"a", "b", "c"},
audience: []string{"projectID", "clientID"},
needRefreshToken: false,
organizationID: "orgID",
},
wantErr: pushErr,
},
@@ -115,7 +118,17 @@ func TestCommands_AddDeviceAuth(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore(t),
}
gotDetails, err := c.AddDeviceAuth(tt.args.ctx, tt.args.clientID, tt.args.deviceCode, tt.args.userCode, tt.args.expires, tt.args.scopes, tt.args.audience, tt.args.needRefreshToken)
gotDetails, err := c.AddDeviceAuth(
tt.args.ctx,
tt.args.clientID,
tt.args.deviceCode,
tt.args.userCode,
tt.args.organizationID,
tt.args.expires,
tt.args.scopes,
tt.args.audience,
tt.args.needRefreshToken,
)
require.ErrorIs(t, err, tt.wantErr)
assertObjectDetails(t, tt.wantDetails, gotDetails)
})
@@ -168,6 +181,35 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
},
wantErr: zerrors.ThrowNotFound(nil, "COMMAND-Hief9", "Errors.DeviceAuth.NotFound"),
},
{
name: "invalid org",
fields: fields{
eventstore: expectEventstore(
expectFilter(eventFromEventPusherWithInstanceID(
"instance1",
deviceauth.NewAddedEvent(
ctx,
deviceauth.NewAggregate("123", "instance1"),
"client_id", "123", "456", now,
[]string{"a", "b", "c"},
[]string{"projectID", "clientID"}, true, "orgID2",
),
)),
),
},
args: args{
ctx, "123", "subj", "orgID",
[]domain.UserAuthMethodType{domain.UserAuthMethodTypePassword},
time.Unix(123, 456), &language.Afrikaans, &domain.UserAgent{
FingerprintID: gu.Ptr("fp1"),
IP: net.ParseIP("1.2.3.4"),
Description: gu.Ptr("firefox"),
Header: http.Header{"foo": []string{"bar"}},
},
"sessionID",
},
wantErr: zerrors.ThrowPreconditionFailed(nil, "COMMAND-3tgws", "Errors.User.NotAllowedOrg"),
},
{
name: "push error",
fields: fields{
@@ -179,7 +221,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
deviceauth.NewAggregate("123", "instance1"),
"client_id", "123", "456", now,
[]string{"a", "b", "c"},
[]string{"projectID", "clientID"}, true,
[]string{"projectID", "clientID"}, true, "",
),
)),
expectPushFailed(pushErr,
@@ -221,7 +263,51 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
deviceauth.NewAggregate("123", "instance1"),
"client_id", "123", "456", now,
[]string{"a", "b", "c"},
[]string{"projectID", "clientID"}, true,
[]string{"projectID", "clientID"}, true, "",
),
)),
expectPush(
deviceauth.NewApprovedEvent(
ctx, deviceauth.NewAggregate("123", "instance1"), "subj", "orgID",
[]domain.UserAuthMethodType{domain.UserAuthMethodTypePassword},
time.Unix(123, 456), &language.Afrikaans, &domain.UserAgent{
FingerprintID: gu.Ptr("fp1"),
IP: net.ParseIP("1.2.3.4"),
Description: gu.Ptr("firefox"),
Header: http.Header{"foo": []string{"bar"}},
},
"sessionID",
),
),
),
},
args: args{
ctx, "123", "subj", "orgID",
[]domain.UserAuthMethodType{domain.UserAuthMethodTypePassword},
time.Unix(123, 456), &language.Afrikaans, &domain.UserAgent{
FingerprintID: gu.Ptr("fp1"),
IP: net.ParseIP("1.2.3.4"),
Description: gu.Ptr("firefox"),
Header: http.Header{"foo": []string{"bar"}},
},
"sessionID",
},
wantDetails: &domain.ObjectDetails{
ResourceOwner: "instance1",
},
},
{
name: "success with organizationID in device auth",
fields: fields{
eventstore: expectEventstore(
expectFilter(eventFromEventPusherWithInstanceID(
"instance1",
deviceauth.NewAddedEvent(
ctx,
deviceauth.NewAggregate("123", "instance1"),
"client_id", "123", "456", now,
[]string{"a", "b", "c"},
[]string{"projectID", "clientID"}, true, "orgID",
),
)),
expectPush(
@@ -317,7 +403,7 @@ func TestCommands_ApproveDeviceAuthFromSession(t *testing.T) {
deviceauth.NewAggregate("deviceCode", "instance1"),
"client_id", "deviceCode", "456", now,
[]string{"a", "b", "c"},
[]string{"projectID", "clientID"}, true,
[]string{"projectID", "clientID"}, true, "",
),
),
eventFromEventPusherWithInstanceID(
@@ -349,7 +435,7 @@ func TestCommands_ApproveDeviceAuthFromSession(t *testing.T) {
deviceauth.NewAggregate("deviceCode", "instance1"),
"client_id", "deviceCode", "456", now,
[]string{"a", "b", "c"},
[]string{"projectID", "clientID"}, true,
[]string{"projectID", "clientID"}, true, "",
),
)),
),
@@ -374,7 +460,7 @@ func TestCommands_ApproveDeviceAuthFromSession(t *testing.T) {
deviceauth.NewAggregate("deviceCode", "instance1"),
"client_id", "deviceCode", "456", now,
[]string{"a", "b", "c"},
[]string{"projectID", "clientID"}, true,
[]string{"projectID", "clientID"}, true, "",
),
)),
expectFilter(),
@@ -400,7 +486,7 @@ func TestCommands_ApproveDeviceAuthFromSession(t *testing.T) {
deviceauth.NewAggregate("deviceCode", "instance1"),
"client_id", "deviceCode", "456", now,
[]string{"a", "b", "c"},
[]string{"projectID", "clientID"}, true,
[]string{"projectID", "clientID"}, true, "",
),
)),
expectFilter(eventFromEventPusherWithInstanceID(
@@ -426,6 +512,57 @@ func TestCommands_ApproveDeviceAuthFromSession(t *testing.T) {
},
wantErr: zerrors.ThrowPermissionDenied(nil, "COMMAND-sGr42", "Errors.Session.Token.Invalid"),
},
{
name: "invalid organization, error",
fields: fields{
eventstore: expectEventstore(
expectFilter(eventFromEventPusherWithInstanceID(
"instance1",
deviceauth.NewAddedEvent(
ctx,
deviceauth.NewAggregate("deviceCode", "instance1"),
"client_id", "deviceCode", "456", now,
[]string{"a", "b", "c"},
[]string{"projectID", "clientID"}, true, "orgID2",
),
)),
expectFilter(
eventFromEventPusherWithInstanceID(
"instance1",
session.NewAddedEvent(ctx,
&session.NewAggregate("sessionID", "instance1").Aggregate,
&domain.UserAgent{
FingerprintID: gu.Ptr("fp1"),
IP: net.ParseIP("1.2.3.4"),
Description: gu.Ptr("firefox"),
Header: http.Header{"foo": []string{"bar"}},
},
)),
eventFromEventPusher(
session.NewUserCheckedEvent(ctx, &session.NewAggregate("sessionID", "instance1").Aggregate,
"userID", "orgID", testNow, &language.Afrikaans),
),
eventFromEventPusher(
session.NewPasswordCheckedEvent(ctx, &session.NewAggregate("sessionID", "instance1").Aggregate,
testNow),
),
eventFromEventPusherWithCreationDateNow(
session.NewLifetimeSetEvent(ctx, &session.NewAggregate("sessionID", "instance1").Aggregate,
2*time.Minute),
),
),
),
tokenVerifier: newMockTokenVerifierValid(),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
ctx,
"deviceCode",
"sessionID",
"sessionToken",
},
wantErr: zerrors.ThrowPreconditionFailed(nil, "COMMAND-D2j34", "Errors.User.NotAllowedOrg"),
},
{
name: "push error",
fields: fields{
@@ -437,7 +574,7 @@ func TestCommands_ApproveDeviceAuthFromSession(t *testing.T) {
deviceauth.NewAggregate("deviceCode", "instance1"),
"client_id", "deviceCode", "456", now,
[]string{"a", "b", "c"},
[]string{"projectID", "clientID"}, true,
[]string{"projectID", "clientID"}, true, "",
),
)),
expectFilter(
@@ -501,7 +638,73 @@ func TestCommands_ApproveDeviceAuthFromSession(t *testing.T) {
deviceauth.NewAggregate("deviceCode", "instance1"),
"client_id", "deviceCode", "456", now,
[]string{"a", "b", "c"},
[]string{"projectID", "clientID"}, true,
[]string{"projectID", "clientID"}, true, "",
),
)),
expectFilter(
eventFromEventPusher(
session.NewAddedEvent(ctx,
&session.NewAggregate("sessionID", "instance1").Aggregate,
&domain.UserAgent{
FingerprintID: gu.Ptr("fp1"),
IP: net.ParseIP("1.2.3.4"),
Description: gu.Ptr("firefox"),
Header: http.Header{"foo": []string{"bar"}},
},
),
),
eventFromEventPusher(
session.NewUserCheckedEvent(ctx, &session.NewAggregate("sessionID", "instance1").Aggregate,
"userID", "orgID", testNow, &language.Afrikaans),
),
eventFromEventPusher(
session.NewPasswordCheckedEvent(ctx, &session.NewAggregate("sessionID", "instance1").Aggregate,
testNow),
),
eventFromEventPusherWithCreationDateNow(
session.NewLifetimeSetEvent(ctx, &session.NewAggregate("sessionID", "instance1").Aggregate,
2*time.Minute),
),
),
expectPush(
deviceauth.NewApprovedEvent(
ctx, deviceauth.NewAggregate("deviceCode", "instance1"), "userID", "orgID",
[]domain.UserAuthMethodType{domain.UserAuthMethodTypePassword},
testNow, &language.Afrikaans, &domain.UserAgent{
FingerprintID: gu.Ptr("fp1"),
IP: net.ParseIP("1.2.3.4"),
Description: gu.Ptr("firefox"),
Header: http.Header{"foo": []string{"bar"}},
},
"sessionID",
),
),
),
tokenVerifier: newMockTokenVerifierValid(),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
ctx,
"deviceCode",
"sessionID",
"sessionToken",
},
wantDetails: &domain.ObjectDetails{
ResourceOwner: "instance1",
},
},
{
name: "authorized with organizationID in device auth",
fields: fields{
eventstore: expectEventstore(
expectFilter(eventFromEventPusherWithInstanceID(
"instance1",
deviceauth.NewAddedEvent(
ctx,
deviceauth.NewAggregate("deviceCode", "instance1"),
"client_id", "deviceCode", "456", now,
[]string{"a", "b", "c"},
[]string{"projectID", "clientID"}, true, "orgID",
),
)),
expectFilter(
@@ -613,7 +816,7 @@ func TestCommands_CancelDeviceAuth(t *testing.T) {
deviceauth.NewAggregate("123", "instance1"),
"client_id", "123", "456", now,
[]string{"a", "b", "c"},
[]string{"projectID", "clientID"}, true,
[]string{"projectID", "clientID"}, true, "",
),
)),
),
@@ -633,7 +836,7 @@ func TestCommands_CancelDeviceAuth(t *testing.T) {
deviceauth.NewAggregate("123", "instance1"),
"client_id", "123", "456", now,
[]string{"a", "b", "c"},
[]string{"projectID", "clientID"}, true,
[]string{"projectID", "clientID"}, true, "",
),
)),
expectPushFailed(pushErr,
@@ -659,7 +862,7 @@ func TestCommands_CancelDeviceAuth(t *testing.T) {
deviceauth.NewAggregate("123", "instance1"),
"client_id", "123", "456", now,
[]string{"a", "b", "c"},
[]string{"projectID", "clientID"}, true,
[]string{"projectID", "clientID"}, true, "",
),
)),
expectPush(
@@ -687,7 +890,7 @@ func TestCommands_CancelDeviceAuth(t *testing.T) {
deviceauth.NewAggregate("123", "instance1"),
"client_id", "123", "456", now,
[]string{"a", "b", "c"},
[]string{"projectID", "clientID"}, true,
[]string{"projectID", "clientID"}, true, "",
),
)),
expectPush(
@@ -767,7 +970,7 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
deviceauth.NewAggregate("123", "instance1"),
"clientID", "123", "456", time.Now().Add(time.Minute),
[]string{"openid", "offline_access"},
[]string{"audience"}, false,
[]string{"audience"}, false, "",
),
),
),
@@ -806,7 +1009,7 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
deviceauth.NewAggregate("123", "instance1"),
"clientID", "123", "456", time.Now().Add(-time.Minute),
[]string{"openid", "offline_access"},
[]string{"audience"}, false,
[]string{"audience"}, false, "",
),
),
),
@@ -835,7 +1038,7 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
deviceauth.NewAggregate("123", "instance1"),
"clientID", "123", "456", time.Now().Add(-time.Minute),
[]string{"openid", "offline_access"},
[]string{"audience"}, false,
[]string{"audience"}, false, "",
),
),
eventFromEventPusherWithInstanceID(
@@ -867,7 +1070,7 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
deviceauth.NewAggregate("123", "instance1"),
"clientID", "123", "456", time.Now().Add(-time.Minute),
[]string{"openid", "offline_access"},
[]string{"audience"}, false,
[]string{"audience"}, false, "",
),
),
eventFromEventPusherWithInstanceID(
@@ -899,7 +1102,7 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
deviceauth.NewAggregate("123", "instance1"),
"clientID", "123", "456", time.Now().Add(-time.Minute),
[]string{"openid", "offline_access"},
[]string{"audience"}, false,
[]string{"audience"}, false, "",
),
),
eventFromEventPusherWithInstanceID(
@@ -937,7 +1140,7 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
deviceauth.NewAggregate("123", "instance1"),
"clientID", "123", "456", time.Now().Add(-time.Minute),
[]string{"openid", "offline_access"},
[]string{"audience"}, false,
[]string{"audience"}, false, "",
),
),
eventFromEventPusherWithInstanceID(
@@ -1001,7 +1204,7 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
deviceauth.NewAggregate("123", "instance1"),
"clientID", "123", "456", time.Now().Add(-time.Minute),
[]string{"openid", "offline_access"},
[]string{"audience"}, false,
[]string{"audience"}, false, "",
),
),
eventFromEventPusherWithInstanceID(
@@ -1098,7 +1301,7 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
deviceauth.NewAggregate("123", "instance1"),
"clientID", "123", "456", time.Now().Add(-time.Minute),
[]string{"openid", "offline_access"},
[]string{"audience"}, false,
[]string{"audience"}, false, "",
),
),
eventFromEventPusherWithInstanceID(
@@ -1202,7 +1405,7 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
deviceauth.NewAggregate("123", "instance1"),
"clientID", "123", "456", time.Now().Add(-time.Minute),
[]string{"openid", "offline_access"},
[]string{"audience"}, true,
[]string{"audience"}, true, "",
),
),
eventFromEventPusherWithInstanceID(
+6
View File
@@ -138,6 +138,7 @@ func TestCommands_CreateOIDCSessionFromAuthRequest(t *testing.T) {
gu.Ptr("hintUserID"),
true,
"issuer",
"",
),
),
eventFromEventPusher(
@@ -183,6 +184,7 @@ func TestCommands_CreateOIDCSessionFromAuthRequest(t *testing.T) {
gu.Ptr("hintUserID"),
true,
"issuer",
"",
),
),
eventFromEventPusher(
@@ -236,6 +238,7 @@ func TestCommands_CreateOIDCSessionFromAuthRequest(t *testing.T) {
gu.Ptr("hintUserID"),
true,
"issuer",
"",
),
),
eventFromEventPusher(
@@ -334,6 +337,7 @@ func TestCommands_CreateOIDCSessionFromAuthRequest(t *testing.T) {
gu.Ptr("hintUserID"),
true,
"issuer",
"",
),
),
eventFromEventPusher(
@@ -468,6 +472,7 @@ func TestCommands_CreateOIDCSessionFromAuthRequest(t *testing.T) {
gu.Ptr("hintUserID"),
true,
"issuer",
"",
),
),
eventFromEventPusher(
@@ -611,6 +616,7 @@ func TestCommands_CreateOIDCSessionFromAuthRequest(t *testing.T) {
gu.Ptr("hintUserID"),
false,
"issuer",
"",
),
),
eventFromEventPusher(
+6 -12
View File
@@ -230,24 +230,18 @@ func (a *AuthRequest) AppendAudIfNotExisting(aud string) {
}
func (a *AuthRequest) GetScopeOrgPrimaryDomain() string {
switch request := a.Request.(type) {
case *AuthRequestOIDC:
for _, scope := range request.Scopes {
if strings.HasPrefix(scope, OrgDomainPrimaryScope) {
return strings.TrimPrefix(scope, OrgDomainPrimaryScope)
}
for _, scope := range a.Request.GetScopes() {
if strings.HasPrefix(scope, OrgDomainPrimaryScope) {
return strings.TrimPrefix(scope, OrgDomainPrimaryScope)
}
}
return ""
}
func (a *AuthRequest) GetScopeOrgID() string {
switch request := a.Request.(type) {
case *AuthRequestOIDC:
for _, scope := range request.Scopes {
if strings.HasPrefix(scope, OrgIDScope) {
return strings.TrimPrefix(scope, OrgIDScope)
}
for _, scope := range a.Request.GetScopes() {
if strings.HasPrefix(scope, OrgIDScope) {
return strings.TrimPrefix(scope, OrgIDScope)
}
}
return ""
+39
View File
@@ -17,6 +17,7 @@ const (
type Request interface {
Type() AuthRequestType
IsValid() bool
GetScopes() []string
}
type AuthRequestType int32
@@ -35,6 +36,18 @@ type AuthRequestOIDC struct {
CodeChallenge *OIDCCodeChallenge
}
func NewAuthRequestOIDC(
scopes []string,
responseType OIDCResponseType,
responseMode OIDCResponseMode,
nonce string,
codeChallenge *OIDCCodeChallenge,
) *AuthRequestOIDC {
return &AuthRequestOIDC{
scopes, responseType, responseMode, nonce, codeChallenge,
}
}
func (a *AuthRequestOIDC) Type() AuthRequestType {
return AuthRequestTypeOIDC
}
@@ -44,6 +57,10 @@ func (a *AuthRequestOIDC) IsValid() bool {
a.CodeChallenge == nil || a.CodeChallenge != nil && a.CodeChallenge.IsValid()
}
func (a *AuthRequestOIDC) GetScopes() []string {
return a.Scopes
}
type AuthRequestSAML struct {
ID string
BindingType string
@@ -61,6 +78,10 @@ func (a *AuthRequestSAML) IsValid() bool {
return true
}
func (*AuthRequestSAML) GetScopes() []string {
return nil
}
type AuthRequestDevice struct {
ClientID string
DeviceCode string
@@ -71,6 +92,20 @@ type AuthRequestDevice struct {
ProjectName string
}
func NewAuthRequestDevice(
clientID string,
deviceCode string,
userCode string,
scopes []string,
audience []string,
appName string,
projectName string,
) *AuthRequestDevice {
return &AuthRequestDevice{
clientID, deviceCode, userCode, scopes, audience, appName, projectName,
}
}
func (*AuthRequestDevice) Type() AuthRequestType {
return AuthRequestTypeDevice
}
@@ -78,3 +113,7 @@ func (*AuthRequestDevice) Type() AuthRequestType {
func (a *AuthRequestDevice) IsValid() bool {
return a.DeviceCode != "" && a.UserCode != ""
}
func (a *AuthRequestDevice) GetScopes() []string {
return a.Scopes
}
+7 -10
View File
@@ -98,8 +98,10 @@ func prepareDeviceAuthQuery() (sq.SelectBuilder, func(*sql.Row) (*domain.AuthReq
LeftJoin(join(ProjectColumnID, AppColumnProjectID)).
PlaceholderFormat(sq.Dollar),
func(row *sql.Row) (*domain.AuthRequestDevice, error) {
dst := new(domain.AuthRequestDevice)
var (
clientID string
deviceCode string
userCode string
scopes database.TextArray[string]
audience database.TextArray[string]
appName sql.NullString
@@ -107,9 +109,9 @@ func prepareDeviceAuthQuery() (sq.SelectBuilder, func(*sql.Row) (*domain.AuthReq
)
err := row.Scan(
&dst.ClientID,
&dst.DeviceCode,
&dst.UserCode,
&clientID,
&deviceCode,
&userCode,
&scopes,
&audience,
&appName,
@@ -121,11 +123,6 @@ func prepareDeviceAuthQuery() (sq.SelectBuilder, func(*sql.Row) (*domain.AuthReq
if err != nil {
return nil, zerrors.ThrowInternal(err, "QUERY-Voo3o", "Errors.Internal")
}
dst.Scopes = scopes
dst.Audience = audience
dst.AppName = appName.String
dst.ProjectName = projectName.String
return dst, nil
return domain.NewAuthRequestDevice(clientID, deviceCode, userCode, scopes, audience, appName.String, projectName.String), nil
}
}
+9 -9
View File
@@ -52,15 +52,15 @@ var (
"appName",
"projectName",
}
expectedDeviceAuth = &domain.AuthRequestDevice{
ClientID: "client-id",
DeviceCode: "device1",
UserCode: "user-code",
Scopes: []string{"a", "b", "c"},
Audience: []string{"projectID", "clientID"},
AppName: "appName",
ProjectName: "projectName",
}
expectedDeviceAuth = domain.NewAuthRequestDevice(
"client-id",
"device1",
"user-code",
[]string{"a", "b", "c"},
[]string{"projectID", "clientID"},
"appName",
"projectName",
)
)
func TestQueries_DeviceAuthRequestByUserCode(t *testing.T) {
@@ -39,6 +39,7 @@ type AddedEvent struct {
HintUserID *string `json:"hint_user_id,omitempty"`
NeedRefreshToken bool `json:"need_refresh_token,omitempty"`
Issuer string `json:"issuer,omitempty"`
OrganizationID string `json:"organization_id,omitempty"`
}
func (e *AddedEvent) Payload() interface{} {
@@ -67,7 +68,8 @@ func NewAddedEvent(ctx context.Context,
loginHint,
hintUserID *string,
needRefreshToken bool,
issuer string,
issuer,
organizationID string,
) *AddedEvent {
return &AddedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
@@ -92,6 +94,7 @@ func NewAddedEvent(ctx context.Context,
HintUserID: hintUserID,
NeedRefreshToken: needRefreshToken,
Issuer: issuer,
OrganizationID: organizationID,
}
}
@@ -29,6 +29,7 @@ type AddedEvent struct {
Audience []string
State domain.DeviceAuthState
NeedRefreshToken bool
OrganizationID string
}
func (e *AddedEvent) SetBaseEvent(b *eventstore.BaseEvent) {
@@ -53,6 +54,7 @@ func NewAddedEvent(
scopes []string,
audience []string,
needRefreshToken bool,
organizationID string,
) *AddedEvent {
return &AddedEvent{
eventstore.NewBaseEventForPush(
@@ -60,6 +62,7 @@ func NewAddedEvent(
),
clientID, deviceCode, userCode, expires, scopes, audience,
domain.DeviceAuthStateInitiated, needRefreshToken,
organizationID,
}
}