feat: add SAML as identity provider (#6454)

* feat: first implementation for saml sp

* fix: add command side instance and org for saml provider

* fix: add query side instance and org for saml provider

* fix: request handling in event and retrieval of finished intent

* fix: add review changes and integration tests

* fix: add integration tests for saml idp

* fix: correct unit tests with review changes

* fix: add saml session unit test

* fix: add saml session unit test

* fix: add saml session unit test

* fix: changes from review

* fix: changes from review

* fix: proto build error

* fix: proto build error

* fix: proto build error

* fix: proto require metadata oneof

* fix: login with saml provider

* fix: integration test for saml assertion

* lint client.go

* fix json tag

* fix: linting

* fix import

* fix: linting

* fix saml idp query

* fix: linting

* lint: try all issues

* revert linting config

* fix: add regenerate endpoints

* fix: translations

* fix mk.yaml

* ignore acs path for user agent cookie

* fix: add AuthFromProvider test for saml

* fix: integration test for saml retrieve information

---------

Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
Stefan Benz
2023-09-29 11:26:14 +02:00
committed by GitHub
parent 2e99d0fe1b
commit 15fd3045e0
82 changed files with 6301 additions and 245 deletions

View File

@@ -4,8 +4,11 @@ import (
"context"
"encoding/base64"
"encoding/json"
"encoding/xml"
"net/url"
"github.com/crewjam/saml"
"github.com/crewjam/saml/samlsp"
"github.com/zitadel/oidc/v2/pkg/oidc"
"github.com/zitadel/zitadel/internal/command/preparation"
@@ -76,12 +79,36 @@ func (c *Commands) CreateIntent(ctx context.Context, idpID, successURL, failureU
return writeModel, writeModelToObjectDetails(&writeModel.WriteModel), nil
}
func (c *Commands) GetProvider(ctx context.Context, idpID string, callbackURL string) (idp.Provider, error) {
func (c *Commands) GetProvider(ctx context.Context, idpID string, idpCallback string, samlRootURL string) (idp.Provider, error) {
writeModel, err := IDPProviderWriteModel(ctx, c.eventstore.Filter, idpID)
if err != nil {
return nil, err
}
return writeModel.ToProvider(callbackURL, c.idpConfigEncryption)
if writeModel.IDPType != domain.IDPTypeSAML {
return writeModel.ToProvider(idpCallback, c.idpConfigEncryption)
}
return writeModel.ToSAMLProvider(
samlRootURL,
c.idpConfigEncryption,
func(ctx context.Context, intentID string) (*samlsp.TrackedRequest, error) {
intent, err := c.GetActiveIntent(ctx, intentID)
if err != nil {
return nil, err
}
return &samlsp.TrackedRequest{
SAMLRequestID: intent.RequestID,
Index: intentID,
URI: intent.SuccessURL.String(),
}, nil
},
func(ctx context.Context, intentID, samlRequestID string) error {
intent, err := c.GetActiveIntent(ctx, intentID)
if err != nil {
return err
}
return c.RequestSAMLIDPIntent(ctx, intent, samlRequestID)
},
)
}
func (c *Commands) GetActiveIntent(ctx context.Context, intentID string) (*IDPIntentWriteModel, error) {
@@ -98,16 +125,18 @@ func (c *Commands) GetActiveIntent(ctx context.Context, intentID string) (*IDPIn
return intent, nil
}
func (c *Commands) AuthURLFromProvider(ctx context.Context, idpID, state string, callbackURL string) (string, error) {
provider, err := c.GetProvider(ctx, idpID, callbackURL)
func (c *Commands) AuthFromProvider(ctx context.Context, idpID, state string, idpCallback, samlRootURL string) (string, bool, error) {
provider, err := c.GetProvider(ctx, idpID, idpCallback, samlRootURL)
if err != nil {
return "", err
return "", false, err
}
session, err := provider.BeginAuth(ctx, state)
if err != nil {
return "", err
return "", false, err
}
return session.GetAuthURL(), nil
content, redirect := session.GetAuth(ctx)
return content, redirect, nil
}
func getIDPIntentWriteModel(ctx context.Context, writeModel *IDPIntentWriteModel, filter preparation.FilterToQueryReducer) error {
@@ -152,6 +181,47 @@ func (c *Commands) SucceedIDPIntent(ctx context.Context, writeModel *IDPIntentWr
return token, nil
}
func (c *Commands) SucceedSAMLIDPIntent(ctx context.Context, writeModel *IDPIntentWriteModel, idpUser idp.User, userID string, assertion *saml.Assertion) (string, error) {
token, err := c.generateIntentToken(writeModel.AggregateID)
if err != nil {
return "", err
}
idpInfo, err := json.Marshal(idpUser)
if err != nil {
return "", err
}
assertionData, err := xml.Marshal(assertion)
if err != nil {
return "", err
}
assertionEnc, err := crypto.Encrypt(assertionData, c.idpConfigEncryption)
if err != nil {
return "", err
}
cmd := idpintent.NewSAMLSucceededEvent(
ctx,
&idpintent.NewAggregate(writeModel.AggregateID, writeModel.ResourceOwner).Aggregate,
idpInfo,
idpUser.GetID(),
idpUser.GetPreferredUsername(),
userID,
assertionEnc,
)
err = c.pushAppendAndReduce(ctx, writeModel, cmd)
if err != nil {
return "", err
}
return token, nil
}
func (c *Commands) RequestSAMLIDPIntent(ctx context.Context, writeModel *IDPIntentWriteModel, requestID string) error {
return c.pushAppendAndReduce(ctx, writeModel, idpintent.NewSAMLRequestEvent(
ctx,
&idpintent.NewAggregate(writeModel.AggregateID, writeModel.ResourceOwner).Aggregate,
requestID,
))
}
func (c *Commands) generateIntentToken(intentID string) (string, error) {
token, err := c.idpConfigEncryption.Encrypt([]byte(intentID))
if err != nil {