mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-16 04:48:04 +00:00
fa8f191812
* feat: v2alpha user service idp endpoints * feat: v2alpha user service intent endpoints * begin idp intents (callback) * some cleanup * runnable idp authentication * cleanup * proto cleanup * retrieve idp info * improve success and failure handling * some unit tests * grpc unit tests * add permission check AddUserIDPLink * feat: v2alpha intent writemodel refactoring * feat: v2alpha intent writemodel refactoring * feat: v2alpha intent writemodel refactoring * provider from write model * fix idp type model and add integration tests * proto cleanup * fix integration test * add missing import * add more integration tests * auth url test * feat: v2alpha intent writemodel refactoring * remove unused functions * check token on RetrieveIdentityProviderInformation * feat: v2alpha intent writemodel refactoring * fix TestServer_RetrieveIdentityProviderInformation * fix test * i18n and linting * feat: v2alpha intent review changes --------- Co-authored-by: Livio Spring <livio.a@gmail.com> Co-authored-by: Tim Möhlmann <tim+github@zitadel.com>
178 lines
5.6 KiB
Go
178 lines
5.6 KiB
Go
package command
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"net/url"
|
|
|
|
"github.com/zitadel/oidc/v2/pkg/oidc"
|
|
|
|
"github.com/zitadel/zitadel/internal/command/preparation"
|
|
"github.com/zitadel/zitadel/internal/crypto"
|
|
"github.com/zitadel/zitadel/internal/domain"
|
|
"github.com/zitadel/zitadel/internal/errors"
|
|
"github.com/zitadel/zitadel/internal/eventstore"
|
|
"github.com/zitadel/zitadel/internal/idp"
|
|
"github.com/zitadel/zitadel/internal/idp/providers/jwt"
|
|
"github.com/zitadel/zitadel/internal/idp/providers/oauth"
|
|
openid "github.com/zitadel/zitadel/internal/idp/providers/oidc"
|
|
"github.com/zitadel/zitadel/internal/repository/idpintent"
|
|
)
|
|
|
|
func (c *Commands) prepareCreateIntent(writeModel *IDPIntentWriteModel, idpID string, successURL, failureURL string) preparation.Validation {
|
|
return func() (_ preparation.CreateCommands, err error) {
|
|
if idpID == "" {
|
|
return nil, errors.ThrowInvalidArgument(nil, "COMMAND-x8j2bk", "Errors.Intent.IDPMissing")
|
|
}
|
|
successURL, err := url.Parse(successURL)
|
|
if err != nil {
|
|
return nil, errors.ThrowInvalidArgument(nil, "COMMAND-x8j3bk", "Errors.Intent.SuccessURLMissing")
|
|
}
|
|
failureURL, err := url.Parse(failureURL)
|
|
if err != nil {
|
|
return nil, errors.ThrowInvalidArgument(nil, "COMMAND-x8j4bk", "Errors.Intent.FailureURLMissing")
|
|
}
|
|
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
|
err = getIDPIntentWriteModel(ctx, writeModel, filter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
exists, err := ExistsIDP(ctx, filter, idpID, writeModel.ResourceOwner)
|
|
if !exists || err != nil {
|
|
return nil, errors.ThrowPreconditionFailed(err, "COMMAND-39n221fs", "Errors.IDPConfig.NotExisting")
|
|
}
|
|
return []eventstore.Command{
|
|
idpintent.NewStartedEvent(ctx, writeModel.aggregate, successURL, failureURL, idpID),
|
|
}, nil
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
func (c *Commands) CreateIntent(ctx context.Context, idpID, successURL, failureURL, resourceOwner string) (string, *domain.ObjectDetails, error) {
|
|
id, err := c.idGenerator.Next()
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
writeModel := NewIDPIntentWriteModel(id, resourceOwner)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
|
|
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareCreateIntent(writeModel, idpID, successURL, failureURL))
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
err = AppendAndReduce(writeModel, pushedEvents...)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
return id, writeModelToObjectDetails(&writeModel.WriteModel), nil
|
|
}
|
|
|
|
func (c *Commands) GetProvider(ctx context.Context, idpID, callbackURL string) (idp.Provider, error) {
|
|
writeModel, err := IDPProviderWriteModel(ctx, c.eventstore.Filter, idpID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return writeModel.ToProvider(callbackURL, c.idpConfigEncryption)
|
|
}
|
|
|
|
func (c *Commands) AuthURLFromProvider(ctx context.Context, idpID, state, callbackURL string) (string, error) {
|
|
provider, err := c.GetProvider(ctx, idpID, callbackURL)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
session, err := provider.BeginAuth(ctx, state)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return session.GetAuthURL(), nil
|
|
}
|
|
|
|
func getIDPIntentWriteModel(ctx context.Context, writeModel *IDPIntentWriteModel, filter preparation.FilterToQueryReducer) error {
|
|
events, err := filter(ctx, writeModel.Query())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(events) == 0 {
|
|
return nil
|
|
}
|
|
writeModel.AppendEvents(events...)
|
|
return writeModel.Reduce()
|
|
}
|
|
|
|
func (c *Commands) SucceedIDPIntent(ctx context.Context, writeModel *IDPIntentWriteModel, idpUser idp.User, idpSession idp.Session, userID string) (string, error) {
|
|
token, err := c.idpConfigEncryption.Encrypt([]byte(writeModel.AggregateID))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
accessToken, idToken, err := tokensForSucceededIDPIntent(idpSession, c.idpConfigEncryption)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
idpInfo, err := json.Marshal(idpUser)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
cmd, err := idpintent.NewSucceededEvent(
|
|
ctx,
|
|
&idpintent.NewAggregate(writeModel.AggregateID, writeModel.ResourceOwner).Aggregate,
|
|
idpInfo,
|
|
userID,
|
|
accessToken,
|
|
idToken,
|
|
)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
err = c.pushAppendAndReduce(ctx, writeModel, cmd)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return base64.RawURLEncoding.EncodeToString(token), nil
|
|
}
|
|
|
|
func (c *Commands) FailIDPIntent(ctx context.Context, writeModel *IDPIntentWriteModel, reason string) error {
|
|
cmd := idpintent.NewFailedEvent(
|
|
ctx,
|
|
&idpintent.NewAggregate(writeModel.AggregateID, writeModel.ResourceOwner).Aggregate,
|
|
reason,
|
|
)
|
|
_, err := c.eventstore.Push(ctx, cmd)
|
|
return err
|
|
}
|
|
|
|
func (c *Commands) GetIntentWriteModel(ctx context.Context, id, resourceOwner string) (*IDPIntentWriteModel, error) {
|
|
writeModel := NewIDPIntentWriteModel(id, resourceOwner)
|
|
err := c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return writeModel, err
|
|
}
|
|
|
|
// tokensForSucceededIDPIntent extracts the oidc.Tokens if available (and encrypts the access_token) for the succeeded event payload
|
|
func tokensForSucceededIDPIntent(session idp.Session, encryptionAlg crypto.EncryptionAlgorithm) (*crypto.CryptoValue, string, error) {
|
|
var tokens *oidc.Tokens[*oidc.IDTokenClaims]
|
|
switch s := session.(type) {
|
|
case *oauth.Session:
|
|
tokens = s.Tokens
|
|
case *openid.Session:
|
|
tokens = s.Tokens
|
|
case *jwt.Session:
|
|
tokens = s.Tokens
|
|
default:
|
|
return nil, "", nil
|
|
}
|
|
if tokens.Token == nil || tokens.AccessToken == "" {
|
|
return nil, tokens.IDToken, nil
|
|
}
|
|
accessToken, err := crypto.Encrypt([]byte(tokens.AccessToken), encryptionAlg)
|
|
return accessToken, tokens.IDToken, err
|
|
}
|