Skip to content

Commit

Permalink
feat: session checks with intent (#6031)
Browse files Browse the repository at this point in the history
* feat: session checks with intent

* feat: session checks with intent

* fix: integration tests for intent session

* fix: integration tests for intent session

* fix merge

* fix: integration tests for intent session

---------

Co-authored-by: Livio Spring <livio.a@gmail.com>
  • Loading branch information
stebenz and livio-a committed Jun 21, 2023
1 parent c12d94f commit 1b5d6ce
Show file tree
Hide file tree
Showing 29 changed files with 727 additions and 153 deletions.
13 changes: 13 additions & 0 deletions internal/api/grpc/session/v2/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ func factorsToPb(s *query.Session) *session.Factors {
User: user,
Password: passwordFactorToPb(s.PasswordFactor),
Passkey: passkeyFactorToPb(s.PasskeyFactor),
Intent: intentFactorToPb(s.IntentFactor),
}
}

Expand All @@ -131,6 +132,15 @@ func passwordFactorToPb(factor query.SessionPasswordFactor) *session.PasswordFac
}
}

func intentFactorToPb(factor query.SessionIntentFactor) *session.IntentFactor {
if factor.IntentCheckedAt.IsZero() {
return nil
}
return &session.IntentFactor{
VerifiedAt: timestamppb.New(factor.IntentCheckedAt),
}
}

func passkeyFactorToPb(factor query.SessionPasskeyFactor) *session.PasskeyFactor {
if factor.PasskeyCheckedAt.IsZero() {
return nil
Expand Down Expand Up @@ -229,6 +239,9 @@ func (s *Server) checksToCommand(ctx context.Context, checks *session.Checks) ([
if password := checks.GetPassword(); password != nil {
sessionChecks = append(sessionChecks, command.CheckPassword(password.GetPassword()))
}
if intent := checks.GetIntent(); intent != nil {
sessionChecks = append(sessionChecks, command.CheckIntent(intent.GetIntentId(), intent.GetToken()))
}
if passkey := checks.GetPasskey(); passkey != nil {
sessionChecks = append(sessionChecks, s.command.CheckPasskey(passkey.GetCredentialAssertionData()))
}
Expand Down
121 changes: 115 additions & 6 deletions internal/api/grpc/session/v2/session_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,21 @@ import (

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"

"github.com/zitadel/zitadel/internal/integration"
object "github.com/zitadel/zitadel/pkg/grpc/object/v2alpha"
session "github.com/zitadel/zitadel/pkg/grpc/session/v2alpha"
user "github.com/zitadel/zitadel/pkg/grpc/user/v2alpha"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

var (
CTX context.Context
Tester *integration.Tester
Client session.SessionServiceClient
User *user.AddHumanUserResponse
CTX context.Context
Tester *integration.Tester
Client session.SessionServiceClient
User *user.AddHumanUserResponse
GenericOAuthIDPID string
)

func TestMain(m *testing.M) {
Expand Down Expand Up @@ -82,6 +84,7 @@ const (
wantUserFactor wantFactor = iota
wantPasswordFactor
wantPasskeyFactor
wantIntentFactor
)

func verifyFactors(t testing.TB, factors *session.Factors, window time.Duration, want []wantFactor) {
Expand All @@ -100,6 +103,10 @@ func verifyFactors(t testing.TB, factors *session.Factors, window time.Duration,
pf := factors.GetPasskey()
assert.NotNil(t, pf)
assert.WithinRange(t, pf.GetVerifiedAt().AsTime(), time.Now().Add(-window), time.Now().Add(window))
case wantIntentFactor:
pf := factors.GetIntent()
assert.NotNil(t, pf)
assert.WithinRange(t, pf.GetVerifiedAt().AsTime(), time.Now().Add(-window), time.Now().Add(window))
}
}
}
Expand Down Expand Up @@ -212,6 +219,108 @@ func TestServer_CreateSession_passkey(t *testing.T) {
verifyCurrentSession(t, createResp.GetSessionId(), updateResp.GetSessionToken(), updateResp.GetDetails().GetSequence(), time.Minute, nil, wantUserFactor, wantPasskeyFactor)
}

func TestServer_CreateSession_successfulIntent(t *testing.T) {
idpID := Tester.AddGenericOAuthProvider(t)

createResp, err := Client.CreateSession(CTX, &session.CreateSessionRequest{
Checks: &session.Checks{
User: &session.CheckUser{
Search: &session.CheckUser_UserId{
UserId: User.GetUserId(),
},
},
},
})
require.NoError(t, err)
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil)

intentID, token, _, _ := Tester.CreateSuccessfulIntent(t, idpID, User.GetUserId(), "id")
updateResp, err := Client.SetSession(CTX, &session.SetSessionRequest{
SessionId: createResp.GetSessionId(),
SessionToken: createResp.GetSessionToken(),
Checks: &session.Checks{
Intent: &session.CheckIntent{
IntentId: intentID,
Token: token,
},
},
})
require.NoError(t, err)
verifyCurrentSession(t, createResp.GetSessionId(), updateResp.GetSessionToken(), updateResp.GetDetails().GetSequence(), time.Minute, nil, wantUserFactor, wantIntentFactor)
}

func TestServer_CreateSession_successfulIntentUnknownUserID(t *testing.T) {
idpID := Tester.AddGenericOAuthProvider(t)

createResp, err := Client.CreateSession(CTX, &session.CreateSessionRequest{
Checks: &session.Checks{
User: &session.CheckUser{
Search: &session.CheckUser_UserId{
UserId: User.GetUserId(),
},
},
},
})
require.NoError(t, err)
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil)

idpUserID := "id"
intentID, token, _, _ := Tester.CreateSuccessfulIntent(t, idpID, "", idpUserID)
updateResp, err := Client.SetSession(CTX, &session.SetSessionRequest{
SessionId: createResp.GetSessionId(),
SessionToken: createResp.GetSessionToken(),
Checks: &session.Checks{
Intent: &session.CheckIntent{
IntentId: intentID,
Token: token,
},
},
})
require.Error(t, err)
Tester.CreateUserIDPlink(CTX, User.GetUserId(), idpUserID, idpID, User.GetUserId())
updateResp, err = Client.SetSession(CTX, &session.SetSessionRequest{
SessionId: createResp.GetSessionId(),
SessionToken: createResp.GetSessionToken(),
Checks: &session.Checks{
Intent: &session.CheckIntent{
IntentId: intentID,
Token: token,
},
},
})
require.NoError(t, err)
verifyCurrentSession(t, createResp.GetSessionId(), updateResp.GetSessionToken(), updateResp.GetDetails().GetSequence(), time.Minute, nil, wantUserFactor, wantIntentFactor)
}

func TestServer_CreateSession_startedIntentFalseToken(t *testing.T) {
idpID := Tester.AddGenericOAuthProvider(t)

createResp, err := Client.CreateSession(CTX, &session.CreateSessionRequest{
Checks: &session.Checks{
User: &session.CheckUser{
Search: &session.CheckUser_UserId{
UserId: User.GetUserId(),
},
},
},
})
require.NoError(t, err)
verifyCurrentSession(t, createResp.GetSessionId(), createResp.GetSessionToken(), createResp.GetDetails().GetSequence(), time.Minute, nil)

intentID := Tester.CreateIntent(t, idpID)
_, err = Client.SetSession(CTX, &session.SetSessionRequest{
SessionId: createResp.GetSessionId(),
SessionToken: createResp.GetSessionToken(),
Checks: &session.Checks{
Intent: &session.CheckIntent{
IntentId: intentID,
Token: "false",
},
},
})
require.Error(t, err)
}

func TestServer_SetSession_flow(t *testing.T) {
var wantFactors []wantFactor

Expand Down
17 changes: 1 addition & 16 deletions internal/api/grpc/user/v2/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package user

import (
"context"
"encoding/base64"
"io"

"golang.org/x/text/language"
Expand Down Expand Up @@ -205,21 +204,7 @@ func intentToIDPInformationPb(intent *command.IDPIntentWriteModel, alg crypto.En
}

func (s *Server) checkIntentToken(token string, intentID string) error {
if token == "" {
return errors.ThrowPermissionDenied(nil, "IDP-Sfefs", "Errors.Intent.InvalidToken")
}
data, err := base64.RawURLEncoding.DecodeString(token)
if err != nil {
return errors.ThrowPermissionDenied(err, "IDP-Swg31", "Errors.Intent.InvalidToken")
}
decryptedToken, err := s.idpAlg.Decrypt(data, s.idpAlg.EncryptionKeyID())
if err != nil {
return errors.ThrowPermissionDenied(err, "IDP-Sf4gt", "Errors.Intent.InvalidToken")
}
if string(decryptedToken) != intentID {
return errors.ThrowPermissionDenied(nil, "IDP-dkje3", "Errors.Intent.InvalidToken")
}
return nil
return crypto.CheckToken(s.idpAlg, token, intentID)
}

func (s *Server) ListAuthenticationMethodTypes(ctx context.Context, req *user.ListAuthenticationMethodTypesRequest) (*user.ListAuthenticationMethodTypesResponse, error) {
Expand Down
73 changes: 6 additions & 67 deletions internal/api/grpc/user/v2/user_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,11 @@ import (
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zitadel/oidc/v2/pkg/oidc"
"golang.org/x/oauth2"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"

"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/api/grpc"
"github.com/zitadel/zitadel/internal/command"
openid "github.com/zitadel/zitadel/internal/idp/providers/oidc"
"github.com/zitadel/zitadel/internal/integration"
"github.com/zitadel/zitadel/internal/repository/idp"
mgmt "github.com/zitadel/zitadel/pkg/grpc/management"
object "github.com/zitadel/zitadel/pkg/grpc/object/v2alpha"
user "github.com/zitadel/zitadel/pkg/grpc/user/v2alpha"
Expand All @@ -50,63 +44,8 @@ func TestMain(m *testing.M) {
}())
}

func createProvider(t *testing.T) string {
ctx := authz.WithInstance(context.Background(), Tester.Instance)
id, _, err := Tester.Commands.AddOrgGenericOAuthProvider(ctx, Tester.Organisation.ID, command.GenericOAuthProvider{
"idp",
"clientID",
"clientSecret",
"https://example.com/oauth/v2/authorize",
"https://example.com/oauth/v2/token",
"https://api.example.com/user",
[]string{"openid", "profile", "email"},
"id",
idp.Options{
IsLinkingAllowed: true,
IsCreationAllowed: true,
IsAutoCreation: true,
IsAutoUpdate: true,
},
})
require.NoError(t, err)
return id
}

func createIntent(t *testing.T, idpID string) string {
ctx := authz.WithInstance(context.Background(), Tester.Instance)
id, _, err := Tester.Commands.CreateIntent(ctx, idpID, "https://example.com/success", "https://example.com/failure", Tester.Organisation.ID)
require.NoError(t, err)
return id
}

func createSuccessfulIntent(t *testing.T, idpID string) (string, string, time.Time, uint64) {
ctx := authz.WithInstance(context.Background(), Tester.Instance)
intentID := createIntent(t, idpID)
writeModel, err := Tester.Commands.GetIntentWriteModel(ctx, intentID, Tester.Organisation.ID)
require.NoError(t, err)
idpUser := openid.NewUser(
&oidc.UserInfo{
Subject: "id",
UserInfoProfile: oidc.UserInfoProfile{
PreferredUsername: "username",
},
},
)
idpSession := &openid.Session{
Tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
Token: &oauth2.Token{
AccessToken: "accessToken",
},
IDToken: "idToken",
},
}
token, err := Tester.Commands.SucceedIDPIntent(ctx, writeModel, idpUser, idpSession, "")
require.NoError(t, err)
return intentID, token, writeModel.ChangeDate, writeModel.ProcessedSequence
}

func TestServer_AddHumanUser(t *testing.T) {
idpID := createProvider(t)
idpID := Tester.AddGenericOAuthProvider(t)
type args struct {
ctx context.Context
req *user.AddHumanUserRequest
Expand Down Expand Up @@ -483,7 +422,7 @@ func TestServer_AddHumanUser(t *testing.T) {
}

func TestServer_AddIDPLink(t *testing.T) {
idpID := createProvider(t)
idpID := Tester.AddGenericOAuthProvider(t)
type args struct {
ctx context.Context
req *user.AddIDPLinkRequest
Expand Down Expand Up @@ -563,7 +502,7 @@ func TestServer_AddIDPLink(t *testing.T) {
}

func TestServer_StartIdentityProviderFlow(t *testing.T) {
idpID := createProvider(t)
idpID := Tester.AddGenericOAuthProvider(t)
type args struct {
ctx context.Context
req *user.StartIdentityProviderFlowRequest
Expand Down Expand Up @@ -627,9 +566,9 @@ func TestServer_StartIdentityProviderFlow(t *testing.T) {
}

func TestServer_RetrieveIdentityProviderInformation(t *testing.T) {
idpID := createProvider(t)
intentID := createIntent(t, idpID)
successfulID, token, changeDate, sequence := createSuccessfulIntent(t, idpID)
idpID := Tester.AddGenericOAuthProvider(t)
intentID := Tester.CreateIntent(t, idpID)
successfulID, token, changeDate, sequence := Tester.CreateSuccessfulIntent(t, idpID, "", "id")
type args struct {
ctx context.Context
req *user.RetrieveIdentityProviderInformationRequest
Expand Down
5 changes: 1 addition & 4 deletions internal/command/idp_intent.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ func (c *Commands) SucceedIDPIntent(ctx context.Context, writeModel *IDPIntentWr
if err != nil {
return "", err
}
cmd, err := idpintent.NewSucceededEvent(
cmd := idpintent.NewSucceededEvent(
ctx,
&idpintent.NewAggregate(writeModel.AggregateID, writeModel.ResourceOwner).Aggregate,
idpInfo,
Expand All @@ -129,9 +129,6 @@ func (c *Commands) SucceedIDPIntent(ctx context.Context, writeModel *IDPIntentWr
accessToken,
idToken,
)
if err != nil {
return "", err
}
err = c.pushAppendAndReduce(ctx, writeModel, cmd)
if err != nil {
return "", err
Expand Down
33 changes: 15 additions & 18 deletions internal/command/idp_intent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -435,24 +435,21 @@ func TestCommands_SucceedIDPIntent(t *testing.T) {
eventstore: eventstoreExpect(t,
expectPush(
eventPusherToEvents(
func() eventstore.Command {
event, _ := idpintent.NewSucceededEvent(
context.Background(),
&idpintent.NewAggregate("id", "ro").Aggregate,
[]byte(`{"sub":"id","preferred_username":"username"}`),
"id",
"username",
"",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("accessToken"),
},
"idToken",
)
return event
}(),
idpintent.NewSucceededEvent(
context.Background(),
&idpintent.NewAggregate("id", "ro").Aggregate,
[]byte(`{"sub":"id","preferred_username":"username"}`),
"id",
"username",
"",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("accessToken"),
},
"idToken",
),
),
),
),
Expand Down

1 comment on commit 1b5d6ce

@vercel
Copy link

@vercel vercel bot commented on 1b5d6ce Jun 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

docs – ./

docs-git-main-zitadel.vercel.app
zitadel-docs.vercel.app
docs-zitadel.vercel.app

Please sign in to comment.