mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 23:27:23 +00:00
Merge branch 'main' into next-rc
This commit is contained in:
commit
99c2247ecb
@ -28,10 +28,6 @@
|
||||
<img src="./docs/static/logos/oidc-cert.png" /></a>
|
||||
</p>
|
||||
|
||||
|Community Meeting|
|
||||
|------------------|
|
||||
|ZITADEL holds bi-weekly community calls. To join the community calls or to watch previous meeting notes and recordings, please visit the [meeting schedule](https://github.com/zitadel/zitadel/blob/main/MEETING_SCHEDULE.md).|
|
||||
|
||||
Are you searching for a user management tool that is quickly set up like Auth0 and open source like Keycloak?
|
||||
|
||||
Do you have a project that requires multi-tenant user management with self-service for your customers?
|
||||
|
@ -2,8 +2,13 @@ package setup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"embed"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"path"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
@ -11,6 +16,13 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
)
|
||||
|
||||
// query filenames
|
||||
const (
|
||||
fileInTxOrderType = "00_in_tx_order_type.sql"
|
||||
fileType = "01_type.sql"
|
||||
fileFunc = "02_func.sql"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed 40/cockroach/*.sql
|
||||
//go:embed 40/postgres/*.sql
|
||||
@ -22,10 +34,6 @@ type InitPushFunc struct {
|
||||
}
|
||||
|
||||
func (mig *InitPushFunc) Execute(ctx context.Context, _ eventstore.Event) (err error) {
|
||||
statements, err := readStatements(initPushFunc, "40", mig.dbClient.Type())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn, err := mig.dbClient.Conn(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -36,7 +44,10 @@ func (mig *InitPushFunc) Execute(ctx context.Context, _ eventstore.Event) (err e
|
||||
// Force the pool to reopen connections to apply the new types
|
||||
mig.dbClient.Pool.Reset()
|
||||
}()
|
||||
|
||||
statements, err := mig.prepareStatements(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, stmt := range statements {
|
||||
logging.WithFields("file", stmt.file, "migration", mig.String()).Info("execute statement")
|
||||
if _, err := conn.ExecContext(ctx, stmt.query); err != nil {
|
||||
@ -50,3 +61,56 @@ func (mig *InitPushFunc) Execute(ctx context.Context, _ eventstore.Event) (err e
|
||||
func (mig *InitPushFunc) String() string {
|
||||
return "40_init_push_func_v4"
|
||||
}
|
||||
|
||||
func (mig *InitPushFunc) prepareStatements(ctx context.Context) ([]statement, error) {
|
||||
funcTmpl, err := template.ParseFS(initPushFunc, mig.filePath(fileFunc))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("prepare steps: %w", err)
|
||||
}
|
||||
typeName, err := mig.inTxOrderType(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("prepare steps: %w", err)
|
||||
}
|
||||
var funcStep strings.Builder
|
||||
err = funcTmpl.Execute(&funcStep, struct {
|
||||
InTxOrderType string
|
||||
}{
|
||||
InTxOrderType: typeName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("prepare steps: %w", err)
|
||||
}
|
||||
typeStatement, err := fs.ReadFile(initPushFunc, mig.filePath(fileType))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("prepare steps: %w", err)
|
||||
}
|
||||
return []statement{
|
||||
{
|
||||
file: fileType,
|
||||
query: string(typeStatement),
|
||||
},
|
||||
{
|
||||
file: fileFunc,
|
||||
query: funcStep.String(),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (mig *InitPushFunc) inTxOrderType(ctx context.Context) (typeName string, err error) {
|
||||
query, err := fs.ReadFile(initPushFunc, mig.filePath(fileInTxOrderType))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get in_tx_order_type: %w", err)
|
||||
}
|
||||
|
||||
err = mig.dbClient.QueryRowContext(ctx, func(row *sql.Row) error {
|
||||
return row.Scan(&typeName)
|
||||
}, string(query))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get in_tx_order_type: %w", err)
|
||||
}
|
||||
return typeName, nil
|
||||
}
|
||||
|
||||
func (mig *InitPushFunc) filePath(fileName string) string {
|
||||
return path.Join("40", mig.dbClient.Type(), fileName)
|
||||
}
|
||||
|
5
cmd/setup/40/cockroach/00_in_tx_order_type.sql
Normal file
5
cmd/setup/40/cockroach/00_in_tx_order_type.sql
Normal file
@ -0,0 +1,5 @@
|
||||
SELECT data_type
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'eventstore'
|
||||
AND table_name = 'events2'
|
||||
AND column_name = 'in_tx_order';
|
10
cmd/setup/40/cockroach/01_type.sql
Normal file
10
cmd/setup/40/cockroach/01_type.sql
Normal file
@ -0,0 +1,10 @@
|
||||
CREATE TYPE IF NOT EXISTS eventstore.command AS (
|
||||
instance_id TEXT
|
||||
, aggregate_type TEXT
|
||||
, aggregate_id TEXT
|
||||
, command_type TEXT
|
||||
, revision INT2
|
||||
, payload JSONB
|
||||
, creator TEXT
|
||||
, owner TEXT
|
||||
);
|
@ -1,15 +1,3 @@
|
||||
-- represents an event to be created.
|
||||
CREATE TYPE IF NOT EXISTS eventstore.command AS (
|
||||
instance_id TEXT
|
||||
, aggregate_type TEXT
|
||||
, aggregate_id TEXT
|
||||
, command_type TEXT
|
||||
, revision INT2
|
||||
, payload JSONB
|
||||
, creator TEXT
|
||||
, owner TEXT
|
||||
);
|
||||
|
||||
CREATE OR REPLACE FUNCTION eventstore.latest_aggregate_state(
|
||||
instance_id TEXT
|
||||
, aggregate_type TEXT
|
||||
@ -98,7 +86,7 @@ BEGIN
|
||||
, ("c").creator
|
||||
, COALESCE(current_owner, ("c").owner) -- AS owner
|
||||
, cluster_logical_timestamp() -- AS position
|
||||
, ordinality::INT -- AS in_tx_order
|
||||
, ordinality::{{ .InTxOrderType }} -- AS in_tx_order
|
||||
FROM
|
||||
UNNEST(commands) WITH ORDINALITY AS c
|
||||
WHERE
|
5
cmd/setup/40/postgres/00_in_tx_order_type.sql
Normal file
5
cmd/setup/40/postgres/00_in_tx_order_type.sql
Normal file
@ -0,0 +1,5 @@
|
||||
SELECT data_type
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'eventstore'
|
||||
AND table_name = 'events2'
|
||||
AND column_name = 'in_tx_order';
|
@ -72,7 +72,7 @@ BEGIN
|
||||
, c.creator
|
||||
, COALESCE(current_owner, c.owner) -- AS owner
|
||||
, EXTRACT(EPOCH FROM NOW()) -- AS position
|
||||
, c.ordinality::INT -- AS in_tx_order
|
||||
, c.ordinality::{{ .InTxOrderType }} -- AS in_tx_order
|
||||
FROM
|
||||
UNNEST(commands) WITH ORDINALITY AS c
|
||||
WHERE
|
||||
|
28
cmd/setup/48_river_queue_repeatable.go
Normal file
28
cmd/setup/48_river_queue_repeatable.go
Normal file
@ -0,0 +1,28 @@
|
||||
package setup
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/queue"
|
||||
)
|
||||
|
||||
type RiverMigrateRepeatable struct {
|
||||
client *database.DB
|
||||
}
|
||||
|
||||
func (mig *RiverMigrateRepeatable) Execute(ctx context.Context, _ eventstore.Event) error {
|
||||
if mig.client.Type() != "postgres" {
|
||||
return nil
|
||||
}
|
||||
return queue.New(mig.client).ExecuteMigrations(ctx)
|
||||
}
|
||||
|
||||
func (mig *RiverMigrateRepeatable) String() string {
|
||||
return "repeatable_migrate_river"
|
||||
}
|
||||
|
||||
func (f *RiverMigrateRepeatable) Check(lastRun map[string]interface{}) bool {
|
||||
return true
|
||||
}
|
@ -4,9 +4,11 @@ import (
|
||||
"context"
|
||||
"embed"
|
||||
_ "embed"
|
||||
"errors"
|
||||
"net/http"
|
||||
"path"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/zitadel/logging"
|
||||
@ -197,6 +199,9 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string)
|
||||
eventstore: eventstoreClient,
|
||||
rolePermissionMappings: config.InternalAuthZ.RolePermissionMappings,
|
||||
},
|
||||
&RiverMigrateRepeatable{
|
||||
client: dbClient,
|
||||
},
|
||||
}
|
||||
|
||||
for _, step := range []migration.Migration{
|
||||
@ -271,7 +276,23 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string)
|
||||
|
||||
func mustExecuteMigration(ctx context.Context, eventstoreClient *eventstore.Eventstore, step migration.Migration, errorMsg string) {
|
||||
err := migration.Migrate(ctx, eventstoreClient, step)
|
||||
logging.WithFields("name", step.String()).OnError(err).Fatal(errorMsg)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
logFields := []any{
|
||||
"name", step.String(),
|
||||
}
|
||||
pgErr := new(pgconn.PgError)
|
||||
if errors.As(err, &pgErr) {
|
||||
logFields = append(logFields,
|
||||
"severity", pgErr.Severity,
|
||||
"code", pgErr.Code,
|
||||
"message", pgErr.Message,
|
||||
"detail", pgErr.Detail,
|
||||
"hint", pgErr.Hint,
|
||||
)
|
||||
}
|
||||
logging.WithFields(logFields...).WithError(err).Fatal(errorMsg)
|
||||
}
|
||||
|
||||
// readStmt reads a single file from the embedded FS,
|
||||
|
@ -34,7 +34,35 @@ Otherwise, your users passwords are sent in clear text through the wire.
|
||||
|
||||
### Basic configuration
|
||||
|
||||
To run LDAP locally to test it with ZITADEL please refer to [OpenLDAP](https://www.openldap.org/) with [slapd](https://www.openldap.org/software/man.cgi?query=slapd).
|
||||
You can run OpenLdap via `docker-compose` using the following:
|
||||
```
|
||||
version: '2'
|
||||
|
||||
networks:
|
||||
my-network:
|
||||
driver: bridge
|
||||
services:
|
||||
openldap:
|
||||
image: bitnami/openldap:latest
|
||||
ports:
|
||||
- '389:1389'
|
||||
environment:
|
||||
- LDAP_ADMIN_USERNAME=admin
|
||||
- LDAP_ADMIN_PASSWORD=Password1!
|
||||
- LDAP_USERS=test
|
||||
- LDAP_PASSWORDS=Password1!
|
||||
- LDAP_ROOT=dc=example,dc=com
|
||||
- LDAP_ADMIN_DN=cn=admin,dc=example,dc=com
|
||||
networks:
|
||||
- my-network
|
||||
volumes:
|
||||
- 'openldap_data:/bitnami/openldap'
|
||||
volumes:
|
||||
openldap_data:
|
||||
driver: local
|
||||
````
|
||||
|
||||
Alternatively, you can run LDAP locally. To run LDAP locally to test it with ZITADEL please refer to [OpenLDAP](https://www.openldap.org/) with [slapd](https://www.openldap.org/software/man.cgi?query=slapd).
|
||||
|
||||
For a quickstart guide please refer to their [official documentation](https://www.openldap.org/doc/admin22/quickstart.html).
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
title: OpenID Connect and Oauth2 web keys
|
||||
sidebar_label: Web keys
|
||||
sidebar_label: Web keys [Beta]
|
||||
---
|
||||
|
||||
Web Keys in ZITADEL are used to sign and verify JSON Web Tokens (JWT).
|
||||
@ -22,6 +22,8 @@ endpoints are called with a JWT access token.
|
||||
|
||||
:::info
|
||||
Web keys are an [experimental](/docs/support/software-release-cycles-support#beta) feature. Be sure to enable the `web_key` [feature](/docs/apis/resources/feature_service_v2/feature-service-set-instance-features) before using it.
|
||||
|
||||
Test the feature and add improvement or bug reports directly to the [github repository](https://github.com/zitadel/zitadel) or let us know your general feedback in the [discord thread](https://discord.com/channels/927474939156643850/1329100936127320175/threads/1332344892629717075)!
|
||||
:::
|
||||
|
||||
### JSON Web Key
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Impersonation and delegation using Token Exchange
|
||||
sidebar_label: Token Exchange
|
||||
sidebar_label: Token Exchange [Beta]
|
||||
---
|
||||
|
||||
import TokenExchangeTypes from "../../apis/openidoauth/_token_exchange_types.mdx";
|
||||
@ -11,6 +11,8 @@ The Token Exchange grant implements [RFC 8693, OAuth 2.0 Token Exchange](https:/
|
||||
|
||||
:::info
|
||||
Token Exchange is currently an [experimental beta](/docs/support/software-release-cycles-support#beta) feature. Be sure to enable it on the [feature API](#feature-api) before using it.
|
||||
|
||||
Test the feature and add improvement or bug reports directly to the [github repository](https://github.com/zitadel/zitadel) or let us know your general feedback in the [discord thread](https://discord.com/channels/927474939156643850/1333448892083208262)!
|
||||
:::
|
||||
|
||||
In this guide we assume that the application performing the token exchange is already in possession of tokens. You should already have a good understanding on the following topics before starting with this guide:
|
||||
|
@ -1,12 +1,14 @@
|
||||
---
|
||||
title: Caches
|
||||
sidebar_label: Caches
|
||||
sidebar_label: Caches [Beta]
|
||||
---
|
||||
|
||||
ZITADEL supports the use of a caches to speed up the lookup of frequently needed objects. As opposed to HTTP caches which might reside between ZITADEL and end-user applications, the cache build into ZITADEL uses active invalidation when an object gets updated. Another difference is that HTTP caches only cache the result of a complete request and the built-in cache stores objects needed for the internal business logic. For example, each request made to ZITADEL needs to retrieve and set [instance](/docs/concepts/structure/instance) information in middleware.
|
||||
|
||||
:::info
|
||||
Caches is currently an [experimental beta](/docs/support/software-release-cycles-support#beta) feature.
|
||||
|
||||
Test the feature and add improvement or bug reports directly to the [github repository](https://github.com/zitadel/zitadel) or let us know your general feedback in the [discord thread](https://discord.com/channels/927474939156643850/1332343909900222506)!
|
||||
:::
|
||||
|
||||
## Configuration
|
||||
|
18
go.mod
18
go.mod
@ -42,7 +42,7 @@ require (
|
||||
github.com/h2non/gock v1.2.0
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||
github.com/improbable-eng/grpc-web v0.15.0
|
||||
github.com/jackc/pgx/v5 v5.7.0
|
||||
github.com/jackc/pgx/v5 v5.7.2
|
||||
github.com/jarcoal/jpath v0.0.0-20140328210829-f76b8b2dbf52
|
||||
github.com/jinzhu/gorm v1.9.16
|
||||
github.com/k3a/html2text v1.2.1
|
||||
@ -63,7 +63,7 @@ require (
|
||||
github.com/sony/sonyflake v1.2.0
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/spf13/viper v1.19.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/superseriousbusiness/exifremove v0.0.0-20210330092427-6acd27eac203
|
||||
github.com/ttacon/libphonenumber v1.2.1
|
||||
github.com/twilio/twilio-go v1.22.2
|
||||
@ -87,7 +87,7 @@ require (
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
|
||||
golang.org/x/net v0.33.0
|
||||
golang.org/x/oauth2 v0.23.0
|
||||
golang.org/x/sync v0.10.0
|
||||
golang.org/x/sync v0.11.0
|
||||
golang.org/x/text v0.21.0
|
||||
google.golang.org/api v0.187.0
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd
|
||||
@ -115,7 +115,7 @@ require (
|
||||
github.com/google/go-tpm v0.9.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba // indirect
|
||||
github.com/google/s2a-go v0.1.7 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect
|
||||
@ -124,11 +124,20 @@ require (
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/riverqueue/river v0.16.0 // indirect
|
||||
github.com/riverqueue/river/riverdriver v0.16.0 // indirect
|
||||
github.com/riverqueue/river/rivershared v0.16.0 // indirect
|
||||
github.com/riverqueue/river/rivertype v0.16.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
github.com/yuin/gopher-lua v1.1.1 // indirect
|
||||
github.com/zenazn/goji v1.0.1 // indirect
|
||||
go.uber.org/goleak v1.3.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d // indirect
|
||||
@ -193,6 +202,7 @@ require (
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.55.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/riverqueue/river/riverdriver/riverpgxv5 v0.16.0
|
||||
github.com/rs/xid v1.5.0 // indirect
|
||||
github.com/russellhaering/goxmldsig v1.4.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
|
28
go.sum
28
go.sum
@ -422,8 +422,12 @@ github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7Ulw
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.7.0 h1:FG6VLIdzvAPhnYqP14sQ2xhFLkiUQHCs6ySqO91kF4g=
|
||||
github.com/jackc/pgx/v5 v5.7.0/go.mod h1:awP1KNnjylvpxHuHP63gzjhnGkI1iw+PMoIwvoleN/8=
|
||||
github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI=
|
||||
github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
|
||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jarcoal/jpath v0.0.0-20140328210829-f76b8b2dbf52 h1:jny9eqYPwkG8IVy7foUoRjQmFLcArCSz+uPsL6KS0HQ=
|
||||
github.com/jarcoal/jpath v0.0.0-20140328210829-f76b8b2dbf52/go.mod h1:RDZ+4PR3mDOtTpVbI0qBE+rdhmtIrtbssiNn38/1OWA=
|
||||
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
|
||||
@ -640,6 +644,16 @@ github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Ung
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
|
||||
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
|
||||
github.com/riverqueue/river v0.16.0 h1:YyQrs0kGgjuABwgat02DPUYS0TMyG2ZFlzvf6+fSFaw=
|
||||
github.com/riverqueue/river v0.16.0/go.mod h1:pEZ8Gc15XyFjVY89nJeL256ub5z18XF7ukYn8ktqQrs=
|
||||
github.com/riverqueue/river/riverdriver v0.16.0 h1:y4Df4e1Xk3Id0nnu1VxHJn9118OzmRHcmvOxM/i1Q30=
|
||||
github.com/riverqueue/river/riverdriver v0.16.0/go.mod h1:7Kdf5HQDrLyLUUqPqXobaK+7zbcMctWeAl7yhg4nHes=
|
||||
github.com/riverqueue/river/riverdriver/riverpgxv5 v0.16.0 h1:6HP296OPN+3ORL9qG1f561pldB5eovkLzfkNIQmaTXI=
|
||||
github.com/riverqueue/river/riverdriver/riverpgxv5 v0.16.0/go.mod h1:MAeBNoTQ+CD3nRvV9mF6iCBfsGJTxYHZeZSP4MYoeUE=
|
||||
github.com/riverqueue/river/rivershared v0.16.0 h1:L1lQ3gMwdIsxA6yF0/PwAdsFP0T82yBD1V03q2GuJDU=
|
||||
github.com/riverqueue/river/rivershared v0.16.0/go.mod h1:y5Xu8Shcp44DUNnEQV4c6oWH4m2OTkSMCe6nRrgzT34=
|
||||
github.com/riverqueue/river/rivertype v0.16.0 h1:iDjNtCiUbXwLraqNEyQdH/OD80f1wTo8Ai6WHYCwRxs=
|
||||
github.com/riverqueue/river/rivertype v0.16.0/go.mod h1:DETcejveWlq6bAb8tHkbgJqmXWVLiFhTiEm8j7co1bE=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
@ -715,10 +729,22 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/superseriousbusiness/exifremove v0.0.0-20210330092427-6acd27eac203 h1:1SWXcTphBQjYGWRRxLFIAR1LVtQEj4eR7xPtyeOVM/c=
|
||||
github.com/superseriousbusiness/exifremove v0.0.0-20210330092427-6acd27eac203/go.mod h1:0Xw5cYMOYpgaWs+OOSx41ugycl2qvKTi9tlMMcZhFyY=
|
||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2 h1:5u+EJUQiosu3JFX0XS0qTf5FznsMOzTjGqavBGuCbo0=
|
||||
github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2/go.mod h1:4kyMkleCiLkgY6z8gK5BkI01ChBtxR0ro3I1ZDcGM3w=
|
||||
@ -900,6 +926,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
@ -177,8 +177,7 @@ func (s *Server) ListProjectChanges(ctx context.Context, req *mgmt_pb.ListProjec
|
||||
}
|
||||
|
||||
func (s *Server) AddProject(ctx context.Context, req *mgmt_pb.AddProjectRequest) (*mgmt_pb.AddProjectResponse, error) {
|
||||
ctxData := authz.GetCtxData(ctx)
|
||||
project, err := s.command.AddProject(ctx, ProjectCreateToDomain(req), ctxData.OrgID, ctxData.UserID)
|
||||
project, err := s.command.AddProject(ctx, ProjectCreateToDomain(req), authz.GetCtxData(ctx).OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -5,11 +5,11 @@ package oidc_test
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -22,92 +22,62 @@ import (
|
||||
"github.com/zitadel/zitadel/pkg/grpc/session/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
CTX context.Context
|
||||
CTXLoginClient context.Context
|
||||
Instance *integration.Instance
|
||||
Client oidc_pb.OIDCServiceClient
|
||||
loginV2 = &app.LoginVersion{Version: &app.LoginVersion_LoginV2{LoginV2: &app.LoginV2{BaseUri: nil}}}
|
||||
)
|
||||
|
||||
const (
|
||||
redirectURI = "oidcintegrationtest://callback"
|
||||
redirectURIImplicit = "http://localhost:9999/callback"
|
||||
logoutRedirectURI = "oidcintegrationtest://logged-out"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(func() int {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
Instance = integration.NewInstance(ctx)
|
||||
Client = Instance.Client.OIDCv2
|
||||
|
||||
CTX = Instance.WithAuthorization(ctx, integration.UserTypeOrgOwner)
|
||||
CTXLoginClient = Instance.WithAuthorization(ctx, integration.UserTypeLogin)
|
||||
return m.Run()
|
||||
}())
|
||||
}
|
||||
|
||||
func TestServer_GetAuthRequest(t *testing.T) {
|
||||
project, err := Instance.CreateProject(CTX)
|
||||
require.NoError(t, err)
|
||||
client, err := Instance.CreateOIDCNativeClient(CTX, redirectURI, logoutRedirectURI, project.GetId(), false)
|
||||
require.NoError(t, err)
|
||||
|
||||
now := time.Now()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
AuthRequestID string
|
||||
ctx context.Context
|
||||
want *oidc_pb.GetAuthRequestResponse
|
||||
wantErr bool
|
||||
name string
|
||||
dep func() (time.Time, string, error)
|
||||
ctx context.Context
|
||||
want *oidc_pb.GetAuthRequestResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Not found",
|
||||
AuthRequestID: "123",
|
||||
ctx: CTX,
|
||||
wantErr: true,
|
||||
name: "Not found",
|
||||
dep: func() (time.Time, string, error) {
|
||||
return time.Now(), "123", nil
|
||||
},
|
||||
ctx: CTX,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "success",
|
||||
AuthRequestID: func() string {
|
||||
authRequestID, err := Instance.CreateOIDCAuthRequest(CTX, client.GetClientId(), Instance.Users[integration.UserTypeOrgOwner].ID, redirectURI)
|
||||
require.NoError(t, err)
|
||||
return authRequestID
|
||||
}(),
|
||||
dep: func() (time.Time, string, error) {
|
||||
return Instance.CreateOIDCAuthRequest(CTX, client.GetClientId(), Instance.Users[integration.UserTypeOrgOwner].ID, redirectURI)
|
||||
},
|
||||
ctx: CTX,
|
||||
},
|
||||
{
|
||||
name: "without login client, no permission",
|
||||
AuthRequestID: func() string {
|
||||
dep: func() (time.Time, string, error) {
|
||||
client, err := Instance.CreateOIDCClientLoginVersion(CTX, redirectURI, logoutRedirectURI, project.GetId(), app.OIDCAppType_OIDC_APP_TYPE_NATIVE, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE, false, loginV2)
|
||||
require.NoError(t, err)
|
||||
authRequestID, err := Instance.CreateOIDCAuthRequestWithoutLoginClientHeader(CTX, client.GetClientId(), redirectURI, "")
|
||||
require.NoError(t, err)
|
||||
return authRequestID
|
||||
}(),
|
||||
return Instance.CreateOIDCAuthRequestWithoutLoginClientHeader(CTX, client.GetClientId(), redirectURI, "")
|
||||
},
|
||||
ctx: CTX,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "without login client, with permission",
|
||||
AuthRequestID: func() string {
|
||||
dep: func() (time.Time, string, error) {
|
||||
client, err := Instance.CreateOIDCClientLoginVersion(CTX, redirectURI, logoutRedirectURI, project.GetId(), app.OIDCAppType_OIDC_APP_TYPE_NATIVE, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE, false, loginV2)
|
||||
require.NoError(t, err)
|
||||
authRequestID, err := Instance.CreateOIDCAuthRequestWithoutLoginClientHeader(CTX, client.GetClientId(), redirectURI, "")
|
||||
require.NoError(t, err)
|
||||
return authRequestID
|
||||
}(),
|
||||
return Instance.CreateOIDCAuthRequestWithoutLoginClientHeader(CTX, client.GetClientId(), redirectURI, "")
|
||||
|
||||
},
|
||||
ctx: CTXLoginClient,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
now, authRequestID, err := tt.dep()
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := Client.GetAuthRequest(tt.ctx, &oidc_pb.GetAuthRequestRequest{
|
||||
AuthRequestId: tt.AuthRequestID,
|
||||
AuthRequestId: authRequestID,
|
||||
})
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
@ -116,7 +86,7 @@ func TestServer_GetAuthRequest(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
authRequest := got.GetAuthRequest()
|
||||
assert.NotNil(t, authRequest)
|
||||
assert.Equal(t, tt.AuthRequestID, authRequest.GetId())
|
||||
assert.Equal(t, authRequestID, authRequest.GetId())
|
||||
assert.WithinRange(t, authRequest.GetCreationDate().AsTime(), now.Add(-time.Second), now.Add(time.Second))
|
||||
assert.Contains(t, authRequest.GetScope(), "openid")
|
||||
})
|
||||
@ -130,16 +100,7 @@ func TestServer_CreateCallback(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
clientV2, err := Instance.CreateOIDCClientLoginVersion(CTX, redirectURI, logoutRedirectURI, project.GetId(), app.OIDCAppType_OIDC_APP_TYPE_NATIVE, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE, false, loginV2)
|
||||
require.NoError(t, err)
|
||||
sessionResp, err := Instance.Client.SessionV2.CreateSession(CTX, &session.CreateSessionRequest{
|
||||
Checks: &session.Checks{
|
||||
User: &session.CheckUser{
|
||||
Search: &session.CheckUser_UserId{
|
||||
UserId: Instance.Users[integration.UserTypeOrgOwner].ID,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
sessionResp := createSession(t, CTX, Instance.Users[integration.UserTypeOrgOwner].ID)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@ -169,7 +130,7 @@ func TestServer_CreateCallback(t *testing.T) {
|
||||
ctx: CTX,
|
||||
req: &oidc_pb.CreateCallbackRequest{
|
||||
AuthRequestId: func() string {
|
||||
authRequestID, err := Instance.CreateOIDCAuthRequest(CTX, client.GetClientId(), Instance.Users[integration.UserTypeOrgOwner].ID, redirectURI)
|
||||
_, authRequestID, err := Instance.CreateOIDCAuthRequest(CTX, client.GetClientId(), Instance.Users[integration.UserTypeOrgOwner].ID, redirectURI)
|
||||
require.NoError(t, err)
|
||||
return authRequestID
|
||||
}(),
|
||||
@ -187,7 +148,7 @@ func TestServer_CreateCallback(t *testing.T) {
|
||||
ctx: CTX,
|
||||
req: &oidc_pb.CreateCallbackRequest{
|
||||
AuthRequestId: func() string {
|
||||
authRequestID, err := Instance.CreateOIDCAuthRequest(CTX, client.GetClientId(), Instance.Users.Get(integration.UserTypeOrgOwner).ID, redirectURI)
|
||||
_, authRequestID, err := Instance.CreateOIDCAuthRequest(CTX, client.GetClientId(), Instance.Users.Get(integration.UserTypeOrgOwner).ID, redirectURI)
|
||||
require.NoError(t, err)
|
||||
return authRequestID
|
||||
}(),
|
||||
@ -205,7 +166,7 @@ func TestServer_CreateCallback(t *testing.T) {
|
||||
ctx: CTX,
|
||||
req: &oidc_pb.CreateCallbackRequest{
|
||||
AuthRequestId: func() string {
|
||||
authRequestID, err := Instance.CreateOIDCAuthRequest(CTX, client.GetClientId(), Instance.Users.Get(integration.UserTypeOrgOwner).ID, redirectURI)
|
||||
_, authRequestID, err := Instance.CreateOIDCAuthRequest(CTX, client.GetClientId(), Instance.Users.Get(integration.UserTypeOrgOwner).ID, redirectURI)
|
||||
require.NoError(t, err)
|
||||
return authRequestID
|
||||
}(),
|
||||
@ -231,7 +192,7 @@ func TestServer_CreateCallback(t *testing.T) {
|
||||
ctx: CTXLoginClient,
|
||||
req: &oidc_pb.CreateCallbackRequest{
|
||||
AuthRequestId: func() string {
|
||||
authRequestID, err := Instance.CreateOIDCAuthRequestWithoutLoginClientHeader(CTX, clientV2.GetClientId(), redirectURI, "")
|
||||
_, authRequestID, err := Instance.CreateOIDCAuthRequestWithoutLoginClientHeader(CTX, clientV2.GetClientId(), redirectURI, "")
|
||||
require.NoError(t, err)
|
||||
return authRequestID
|
||||
}(),
|
||||
@ -257,7 +218,7 @@ func TestServer_CreateCallback(t *testing.T) {
|
||||
ctx: CTX,
|
||||
req: &oidc_pb.CreateCallbackRequest{
|
||||
AuthRequestId: func() string {
|
||||
authRequestID, err := Instance.CreateOIDCAuthRequest(CTX, client.GetClientId(), Instance.Users.Get(integration.UserTypeOrgOwner).ID, redirectURI)
|
||||
_, authRequestID, err := Instance.CreateOIDCAuthRequest(CTX, client.GetClientId(), Instance.Users.Get(integration.UserTypeOrgOwner).ID, redirectURI)
|
||||
require.NoError(t, err)
|
||||
return authRequestID
|
||||
}(),
|
||||
@ -282,7 +243,7 @@ func TestServer_CreateCallback(t *testing.T) {
|
||||
ctx: CTX,
|
||||
req: &oidc_pb.CreateCallbackRequest{
|
||||
AuthRequestId: func() string {
|
||||
authRequestID, err := Instance.CreateOIDCAuthRequestWithoutLoginClientHeader(CTX, clientV2.GetClientId(), redirectURI, "")
|
||||
_, authRequestID, err := Instance.CreateOIDCAuthRequestWithoutLoginClientHeader(CTX, clientV2.GetClientId(), redirectURI, "")
|
||||
require.NoError(t, err)
|
||||
return authRequestID
|
||||
}(),
|
||||
@ -300,7 +261,7 @@ func TestServer_CreateCallback(t *testing.T) {
|
||||
ctx: CTXLoginClient,
|
||||
req: &oidc_pb.CreateCallbackRequest{
|
||||
AuthRequestId: func() string {
|
||||
authRequestID, err := Instance.CreateOIDCAuthRequestWithoutLoginClientHeader(CTX, clientV2.GetClientId(), redirectURI, "")
|
||||
_, authRequestID, err := Instance.CreateOIDCAuthRequestWithoutLoginClientHeader(CTX, clientV2.GetClientId(), redirectURI, "")
|
||||
require.NoError(t, err)
|
||||
return authRequestID
|
||||
}(),
|
||||
@ -390,3 +351,324 @@ func TestServer_CreateCallback(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_CreateCallback_Permission(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
dep func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest
|
||||
want *oidc_pb.CreateCallbackResponse
|
||||
wantURL *url.URL
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "usergrant to project and different resourceowner with different project grant",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest {
|
||||
projectID, clientID := createOIDCApplication(ctx, t, true, true)
|
||||
projectID2, _ := createOIDCApplication(ctx, t, true, true)
|
||||
|
||||
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
|
||||
Instance.CreateProjectGrant(ctx, projectID2, orgResp.GetOrganizationId())
|
||||
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
|
||||
Instance.CreateProjectUserGrant(t, ctx, projectID, user.GetUserId())
|
||||
|
||||
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "usergrant to project and different resourceowner with project grant",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest {
|
||||
projectID, clientID := createOIDCApplication(ctx, t, true, true)
|
||||
|
||||
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
|
||||
Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
|
||||
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
|
||||
Instance.CreateProjectUserGrant(t, ctx, projectID, user.GetUserId())
|
||||
|
||||
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
|
||||
},
|
||||
want: &oidc_pb.CreateCallbackResponse{
|
||||
CallbackUrl: `oidcintegrationtest:\/\/callback\?code=(.*)&state=state`,
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "usergrant to project grant and different resourceowner with project grant",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest {
|
||||
projectID, clientID := createOIDCApplication(ctx, t, true, true)
|
||||
|
||||
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
|
||||
projectGrantResp := Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
|
||||
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
|
||||
Instance.CreateProjectGrantUserGrant(ctx, orgResp.GetOrganizationId(), projectID, projectGrantResp.GetGrantId(), user.GetUserId())
|
||||
|
||||
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
|
||||
},
|
||||
want: &oidc_pb.CreateCallbackResponse{
|
||||
CallbackUrl: `oidcintegrationtest:\/\/callback\?code=(.*)&state=state`,
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no usergrant and different resourceowner",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest {
|
||||
_, clientID := createOIDCApplication(ctx, t, true, true)
|
||||
|
||||
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
|
||||
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
|
||||
|
||||
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "no usergrant and same resourceowner",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest {
|
||||
_, clientID := createOIDCApplication(ctx, t, true, true)
|
||||
user := Instance.CreateHumanUser(ctx)
|
||||
|
||||
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "usergrant and different resourceowner",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest {
|
||||
projectID, clientID := createOIDCApplication(ctx, t, true, true)
|
||||
|
||||
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
|
||||
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
|
||||
Instance.CreateProjectUserGrant(t, ctx, projectID, user.GetUserId())
|
||||
|
||||
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "usergrant and same resourceowner",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest {
|
||||
projectID, clientID := createOIDCApplication(ctx, t, true, true)
|
||||
user := Instance.CreateHumanUser(ctx)
|
||||
Instance.CreateProjectUserGrant(t, ctx, projectID, user.GetUserId())
|
||||
|
||||
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
|
||||
},
|
||||
want: &oidc_pb.CreateCallbackResponse{
|
||||
CallbackUrl: `oidcintegrationtest:\/\/callback\?code=(.*)&state=state`,
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "projectRoleCheck, usergrant and same resourceowner",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest {
|
||||
projectID, clientID := createOIDCApplication(ctx, t, true, false)
|
||||
user := Instance.CreateHumanUser(ctx)
|
||||
Instance.CreateProjectUserGrant(t, ctx, projectID, user.GetUserId())
|
||||
|
||||
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
|
||||
},
|
||||
want: &oidc_pb.CreateCallbackResponse{
|
||||
CallbackUrl: `oidcintegrationtest:\/\/callback\?code=(.*)&state=state`,
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "projectRoleCheck, no usergrant and same resourceowner",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest {
|
||||
_, clientID := createOIDCApplication(ctx, t, true, false)
|
||||
user := Instance.CreateHumanUser(ctx)
|
||||
|
||||
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "projectRoleCheck, usergrant and different resourceowner",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest {
|
||||
projectID, clientID := createOIDCApplication(ctx, t, true, false)
|
||||
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
|
||||
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
|
||||
Instance.CreateProjectUserGrant(t, ctx, projectID, user.GetUserId())
|
||||
|
||||
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
|
||||
},
|
||||
want: &oidc_pb.CreateCallbackResponse{
|
||||
CallbackUrl: `oidcintegrationtest:\/\/callback\?code=(.*)&state=state`,
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "projectRoleCheck, no usergrant and different resourceowner",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest {
|
||||
_, clientID := createOIDCApplication(ctx, t, true, false)
|
||||
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
|
||||
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
|
||||
|
||||
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "projectRoleCheck, usergrant on project grant and different resourceowner",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest {
|
||||
projectID, clientID := createOIDCApplication(ctx, t, true, false)
|
||||
|
||||
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
|
||||
projectGrantResp := Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
|
||||
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
|
||||
Instance.CreateProjectGrantUserGrant(ctx, orgResp.GetOrganizationId(), projectID, projectGrantResp.GetGrantId(), user.GetUserId())
|
||||
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
|
||||
},
|
||||
want: &oidc_pb.CreateCallbackResponse{
|
||||
CallbackUrl: `oidcintegrationtest:\/\/callback\?code=(.*)&state=state`,
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "projectRoleCheck, no usergrant on project grant and different resourceowner",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest {
|
||||
projectID, clientID := createOIDCApplication(ctx, t, true, false)
|
||||
|
||||
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
|
||||
Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
|
||||
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
|
||||
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "hasProjectCheck, same resourceowner",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest {
|
||||
user := Instance.CreateHumanUser(ctx)
|
||||
_, clientID := createOIDCApplication(ctx, t, false, true)
|
||||
|
||||
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
|
||||
},
|
||||
want: &oidc_pb.CreateCallbackResponse{
|
||||
CallbackUrl: `oidcintegrationtest:\/\/callback\?code=(.*)&state=state`,
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "hasProjectCheck, different resourceowner",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest {
|
||||
_, clientID := createOIDCApplication(ctx, t, false, true)
|
||||
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
|
||||
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
|
||||
|
||||
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "hasProjectCheck, different resourceowner with project grant",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest {
|
||||
projectID, clientID := createOIDCApplication(ctx, t, false, true)
|
||||
|
||||
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
|
||||
Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
|
||||
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
|
||||
|
||||
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
|
||||
},
|
||||
want: &oidc_pb.CreateCallbackResponse{
|
||||
CallbackUrl: `oidcintegrationtest:\/\/callback\?code=(.*)&state=state`,
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req := tt.dep(IAMCTX, t)
|
||||
|
||||
got, err := Client.CreateCallback(tt.ctx, req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
integration.AssertDetails(t, tt.want, got)
|
||||
if tt.want != nil {
|
||||
assert.Regexp(t, regexp.MustCompile(tt.want.CallbackUrl), got.GetCallbackUrl())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func createSession(t *testing.T, ctx context.Context, userID string) *session.CreateSessionResponse {
|
||||
sessionResp, err := Instance.Client.SessionV2.CreateSession(ctx, &session.CreateSessionRequest{
|
||||
Checks: &session.Checks{
|
||||
User: &session.CheckUser{
|
||||
Search: &session.CheckUser_UserId{
|
||||
UserId: userID,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return sessionResp
|
||||
}
|
||||
|
||||
func createSessionAndAuthRequestForCallback(ctx context.Context, t *testing.T, clientID, loginClient, userID string) *oidc_pb.CreateCallbackRequest {
|
||||
_, authRequestID, err := Instance.CreateOIDCAuthRequest(ctx, clientID, loginClient, redirectURI)
|
||||
require.NoError(t, err)
|
||||
sessionResp := createSession(t, ctx, userID)
|
||||
return &oidc_pb.CreateCallbackRequest{
|
||||
AuthRequestId: authRequestID,
|
||||
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
|
||||
Session: &oidc_pb.Session{
|
||||
SessionId: sessionResp.GetSessionId(),
|
||||
SessionToken: sessionResp.GetSessionToken(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func createOIDCApplication(ctx context.Context, t *testing.T, projectRoleCheck, hasProjectCheck bool) (string, string) {
|
||||
project, err := Instance.CreateProjectWithPermissionCheck(ctx, projectRoleCheck, hasProjectCheck)
|
||||
require.NoError(t, err)
|
||||
clientV2, err := Instance.CreateOIDCClientLoginVersion(ctx, redirectURI, logoutRedirectURI, project.GetId(), app.OIDCAppType_OIDC_APP_TYPE_NATIVE, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE, false, loginV2)
|
||||
require.NoError(t, err)
|
||||
return project.GetId(), clientV2.GetClientId()
|
||||
}
|
||||
|
44
internal/api/grpc/oidc/v2/integration_test/server_test.go
Normal file
44
internal/api/grpc/oidc/v2/integration_test/server_test.go
Normal file
@ -0,0 +1,44 @@
|
||||
//go:build integration
|
||||
|
||||
package oidc_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/app"
|
||||
oidc_pb "github.com/zitadel/zitadel/pkg/grpc/oidc/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
CTX context.Context
|
||||
CTXLoginClient context.Context
|
||||
IAMCTX context.Context
|
||||
Instance *integration.Instance
|
||||
Client oidc_pb.OIDCServiceClient
|
||||
loginV2 = &app.LoginVersion{Version: &app.LoginVersion_LoginV2{LoginV2: &app.LoginV2{BaseUri: nil}}}
|
||||
)
|
||||
|
||||
const (
|
||||
redirectURI = "oidcintegrationtest://callback"
|
||||
redirectURIImplicit = "http://localhost:9999/callback"
|
||||
logoutRedirectURI = "oidcintegrationtest://logged-out"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(func() int {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
Instance = integration.NewInstance(ctx)
|
||||
Client = Instance.Client.OIDCv2
|
||||
|
||||
IAMCTX = Instance.WithAuthorization(ctx, integration.UserTypeIAMOwner)
|
||||
CTX = Instance.WithAuthorization(ctx, integration.UserTypeOrgOwner)
|
||||
CTXLoginClient = Instance.WithAuthorization(ctx, integration.UserTypeLogin)
|
||||
return m.Run()
|
||||
}())
|
||||
}
|
@ -73,6 +73,20 @@ func promptToPb(p domain.Prompt) oidc_pb.Prompt {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) checkPermission(ctx context.Context, clientID string, userID string) error {
|
||||
permission, err := s.query.CheckProjectPermissionByClientID(ctx, clientID, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !permission.HasProjectChecked {
|
||||
return zerrors.ThrowPermissionDenied(nil, "OIDC-foSyH49RvL", "Errors.User.ProjectRequired")
|
||||
}
|
||||
if !permission.ProjectRoleChecked {
|
||||
return zerrors.ThrowPermissionDenied(nil, "OIDC-foSyH49RvL", "Errors.User.GrantRequired")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) CreateCallback(ctx context.Context, req *oidc_pb.CreateCallbackRequest) (*oidc_pb.CreateCallbackResponse, error) {
|
||||
switch v := req.GetCallbackKind().(type) {
|
||||
case *oidc_pb.CreateCallbackRequest_Error:
|
||||
@ -101,7 +115,7 @@ func (s *Server) failAuthRequest(ctx context.Context, authRequestID string, ae *
|
||||
}
|
||||
|
||||
func (s *Server) linkSessionToAuthRequest(ctx context.Context, authRequestID string, session *oidc_pb.Session) (*oidc_pb.CreateCallbackResponse, error) {
|
||||
details, aar, err := s.command.LinkSessionToAuthRequest(ctx, authRequestID, session.GetSessionId(), session.GetSessionToken(), true)
|
||||
details, aar, err := s.command.LinkSessionToAuthRequest(ctx, authRequestID, session.GetSessionId(), session.GetSessionToken(), true, s.checkPermission)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -5,76 +5,79 @@ package oidc_test
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/app"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
|
||||
oidc_pb "github.com/zitadel/zitadel/pkg/grpc/oidc/v2beta"
|
||||
session "github.com/zitadel/zitadel/pkg/grpc/session/v2beta"
|
||||
)
|
||||
|
||||
var (
|
||||
CTX context.Context
|
||||
Instance *integration.Instance
|
||||
Client oidc_pb.OIDCServiceClient
|
||||
)
|
||||
|
||||
const (
|
||||
redirectURI = "oidcintegrationtest://callback"
|
||||
redirectURIImplicit = "http://localhost:9999/callback"
|
||||
logoutRedirectURI = "oidcintegrationtest://logged-out"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(func() int {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
Instance = integration.NewInstance(ctx)
|
||||
Client = Instance.Client.OIDCv2beta
|
||||
|
||||
CTX = Instance.WithAuthorization(ctx, integration.UserTypeOrgOwner)
|
||||
return m.Run()
|
||||
}())
|
||||
}
|
||||
|
||||
func TestServer_GetAuthRequest(t *testing.T) {
|
||||
project, err := Instance.CreateProject(CTX)
|
||||
require.NoError(t, err)
|
||||
client, err := Instance.CreateOIDCNativeClient(CTX, redirectURI, logoutRedirectURI, project.GetId(), false)
|
||||
require.NoError(t, err)
|
||||
authRequestID, err := Instance.CreateOIDCAuthRequest(CTX, client.GetClientId(), Instance.Users.Get(integration.UserTypeOrgOwner).ID, redirectURI)
|
||||
require.NoError(t, err)
|
||||
now := time.Now()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
AuthRequestID string
|
||||
want *oidc_pb.GetAuthRequestResponse
|
||||
wantErr bool
|
||||
name string
|
||||
dep func() (time.Time, string, error)
|
||||
ctx context.Context
|
||||
want *oidc_pb.GetAuthRequestResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Not found",
|
||||
AuthRequestID: "123",
|
||||
wantErr: true,
|
||||
name: "Not found",
|
||||
dep: func() (time.Time, string, error) {
|
||||
return time.Now(), "123", nil
|
||||
},
|
||||
ctx: CTX,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "success",
|
||||
AuthRequestID: authRequestID,
|
||||
name: "success",
|
||||
dep: func() (time.Time, string, error) {
|
||||
return Instance.CreateOIDCAuthRequest(CTX, client.GetClientId(), Instance.Users[integration.UserTypeOrgOwner].ID, redirectURI)
|
||||
},
|
||||
ctx: CTX,
|
||||
},
|
||||
{
|
||||
name: "without login client, no permission",
|
||||
dep: func() (time.Time, string, error) {
|
||||
client, err := Instance.CreateOIDCClientLoginVersion(CTX, redirectURI, logoutRedirectURI, project.GetId(), app.OIDCAppType_OIDC_APP_TYPE_NATIVE, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE, false, loginV2)
|
||||
require.NoError(t, err)
|
||||
return Instance.CreateOIDCAuthRequestWithoutLoginClientHeader(CTX, client.GetClientId(), redirectURI, "")
|
||||
},
|
||||
ctx: CTX,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "without login client, with permission",
|
||||
dep: func() (time.Time, string, error) {
|
||||
client, err := Instance.CreateOIDCClientLoginVersion(CTX, redirectURI, logoutRedirectURI, project.GetId(), app.OIDCAppType_OIDC_APP_TYPE_NATIVE, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE, false, loginV2)
|
||||
require.NoError(t, err)
|
||||
return Instance.CreateOIDCAuthRequestWithoutLoginClientHeader(CTX, client.GetClientId(), redirectURI, "")
|
||||
|
||||
},
|
||||
ctx: CTXLoginClient,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := Client.GetAuthRequest(CTX, &oidc_pb.GetAuthRequestRequest{
|
||||
AuthRequestId: tt.AuthRequestID,
|
||||
now, authRequestID, err := tt.dep()
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := Client.GetAuthRequest(tt.ctx, &oidc_pb.GetAuthRequestRequest{
|
||||
AuthRequestId: authRequestID,
|
||||
})
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
@ -95,19 +98,13 @@ func TestServer_CreateCallback(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
client, err := Instance.CreateOIDCNativeClient(CTX, redirectURI, logoutRedirectURI, project.GetId(), false)
|
||||
require.NoError(t, err)
|
||||
sessionResp, err := Instance.Client.SessionV2beta.CreateSession(CTX, &session.CreateSessionRequest{
|
||||
Checks: &session.Checks{
|
||||
User: &session.CheckUser{
|
||||
Search: &session.CheckUser_UserId{
|
||||
UserId: Instance.Users.Get(integration.UserTypeOrgOwner).ID,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
clientV2, err := Instance.CreateOIDCClientLoginVersion(CTX, redirectURI, logoutRedirectURI, project.GetId(), app.OIDCAppType_OIDC_APP_TYPE_NATIVE, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE, false, loginV2)
|
||||
require.NoError(t, err)
|
||||
sessionResp := createSession(t, CTX, Instance.Users[integration.UserTypeOrgOwner].ID)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
req *oidc_pb.CreateCallbackRequest
|
||||
AuthError string
|
||||
want *oidc_pb.CreateCallbackResponse
|
||||
@ -116,6 +113,7 @@ func TestServer_CreateCallback(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "Not found",
|
||||
ctx: CTX,
|
||||
req: &oidc_pb.CreateCallbackRequest{
|
||||
AuthRequestId: "123",
|
||||
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
|
||||
@ -129,9 +127,10 @@ func TestServer_CreateCallback(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "session not found",
|
||||
ctx: CTX,
|
||||
req: &oidc_pb.CreateCallbackRequest{
|
||||
AuthRequestId: func() string {
|
||||
authRequestID, err := Instance.CreateOIDCAuthRequest(CTX, client.GetClientId(), Instance.Users.Get(integration.UserTypeOrgOwner).ID, redirectURI)
|
||||
_, authRequestID, err := Instance.CreateOIDCAuthRequest(CTX, client.GetClientId(), Instance.Users[integration.UserTypeOrgOwner].ID, redirectURI)
|
||||
require.NoError(t, err)
|
||||
return authRequestID
|
||||
}(),
|
||||
@ -146,9 +145,10 @@ func TestServer_CreateCallback(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "session token invalid",
|
||||
ctx: CTX,
|
||||
req: &oidc_pb.CreateCallbackRequest{
|
||||
AuthRequestId: func() string {
|
||||
authRequestID, err := Instance.CreateOIDCAuthRequest(CTX, client.GetClientId(), Instance.Users.Get(integration.UserTypeOrgOwner).ID, redirectURI)
|
||||
_, authRequestID, err := Instance.CreateOIDCAuthRequest(CTX, client.GetClientId(), Instance.Users.Get(integration.UserTypeOrgOwner).ID, redirectURI)
|
||||
require.NoError(t, err)
|
||||
return authRequestID
|
||||
}(),
|
||||
@ -163,9 +163,36 @@ func TestServer_CreateCallback(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "fail callback",
|
||||
ctx: CTX,
|
||||
req: &oidc_pb.CreateCallbackRequest{
|
||||
AuthRequestId: func() string {
|
||||
authRequestID, err := Instance.CreateOIDCAuthRequest(CTX, client.GetClientId(), Instance.Users.Get(integration.UserTypeOrgOwner).ID, redirectURI)
|
||||
_, authRequestID, err := Instance.CreateOIDCAuthRequest(CTX, client.GetClientId(), Instance.Users.Get(integration.UserTypeOrgOwner).ID, redirectURI)
|
||||
require.NoError(t, err)
|
||||
return authRequestID
|
||||
}(),
|
||||
CallbackKind: &oidc_pb.CreateCallbackRequest_Error{
|
||||
Error: &oidc_pb.AuthorizationError{
|
||||
Error: oidc_pb.ErrorReason_ERROR_REASON_ACCESS_DENIED,
|
||||
ErrorDescription: gu.Ptr("nope"),
|
||||
ErrorUri: gu.Ptr("https://example.com/docs"),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &oidc_pb.CreateCallbackResponse{
|
||||
CallbackUrl: regexp.QuoteMeta(`oidcintegrationtest://callback?error=access_denied&error_description=nope&error_uri=https%3A%2F%2Fexample.com%2Fdocs&state=state`),
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Instance.ID(),
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "fail callback, no login client header",
|
||||
ctx: CTXLoginClient,
|
||||
req: &oidc_pb.CreateCallbackRequest{
|
||||
AuthRequestId: func() string {
|
||||
_, authRequestID, err := Instance.CreateOIDCAuthRequestWithoutLoginClientHeader(CTX, clientV2.GetClientId(), redirectURI, "")
|
||||
require.NoError(t, err)
|
||||
return authRequestID
|
||||
}(),
|
||||
@ -188,9 +215,53 @@ func TestServer_CreateCallback(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "code callback",
|
||||
ctx: CTX,
|
||||
req: &oidc_pb.CreateCallbackRequest{
|
||||
AuthRequestId: func() string {
|
||||
authRequestID, err := Instance.CreateOIDCAuthRequest(CTX, client.GetClientId(), Instance.Users.Get(integration.UserTypeOrgOwner).ID, redirectURI)
|
||||
_, authRequestID, err := Instance.CreateOIDCAuthRequest(CTX, client.GetClientId(), Instance.Users.Get(integration.UserTypeOrgOwner).ID, redirectURI)
|
||||
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,
|
||||
},
|
||||
{
|
||||
name: "code callback, no login client header, no permission, error",
|
||||
ctx: CTX,
|
||||
req: &oidc_pb.CreateCallbackRequest{
|
||||
AuthRequestId: func() string {
|
||||
_, authRequestID, err := Instance.CreateOIDCAuthRequestWithoutLoginClientHeader(CTX, clientV2.GetClientId(), redirectURI, "")
|
||||
require.NoError(t, err)
|
||||
return authRequestID
|
||||
}(),
|
||||
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
|
||||
Session: &oidc_pb.Session{
|
||||
SessionId: sessionResp.GetSessionId(),
|
||||
SessionToken: sessionResp.GetSessionToken(),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "code callback, no login client header, with permission",
|
||||
ctx: CTXLoginClient,
|
||||
req: &oidc_pb.CreateCallbackRequest{
|
||||
AuthRequestId: func() string {
|
||||
_, authRequestID, err := Instance.CreateOIDCAuthRequestWithoutLoginClientHeader(CTX, clientV2.GetClientId(), redirectURI, "")
|
||||
require.NoError(t, err)
|
||||
return authRequestID
|
||||
}(),
|
||||
@ -212,6 +283,7 @@ func TestServer_CreateCallback(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "implicit",
|
||||
ctx: CTX,
|
||||
req: &oidc_pb.CreateCallbackRequest{
|
||||
AuthRequestId: func() string {
|
||||
client, err := Instance.CreateOIDCImplicitFlowClient(CTX, redirectURIImplicit, nil)
|
||||
@ -236,10 +308,37 @@ func TestServer_CreateCallback(t *testing.T) {
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "implicit, no login client header",
|
||||
ctx: CTXLoginClient,
|
||||
req: &oidc_pb.CreateCallbackRequest{
|
||||
AuthRequestId: func() string {
|
||||
clientV2, err := Instance.CreateOIDCImplicitFlowClient(CTX, redirectURIImplicit, loginV2)
|
||||
require.NoError(t, err)
|
||||
authRequestID, err := Instance.CreateOIDCAuthRequestImplicitWithoutLoginClientHeader(CTX, clientV2.GetClientId(), redirectURIImplicit)
|
||||
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: `http:\/\/localhost:9999\/callback#access_token=(.*)&expires_in=(.*)&id_token=(.*)&state=state&token_type=Bearer`,
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Instance.ID(),
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := Client.CreateCallback(CTX, tt.req)
|
||||
got, err := Client.CreateCallback(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
@ -252,3 +351,324 @@ func TestServer_CreateCallback(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_CreateCallback_Permission(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
dep func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest
|
||||
want *oidc_pb.CreateCallbackResponse
|
||||
wantURL *url.URL
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "usergrant to project and different resourceowner with different project grant",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest {
|
||||
projectID, clientID := createOIDCApplication(ctx, t, true, true)
|
||||
projectID2, _ := createOIDCApplication(ctx, t, true, true)
|
||||
|
||||
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
|
||||
Instance.CreateProjectGrant(ctx, projectID2, orgResp.GetOrganizationId())
|
||||
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
|
||||
Instance.CreateProjectUserGrant(t, ctx, projectID, user.GetUserId())
|
||||
|
||||
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "usergrant to project and different resourceowner with project grant",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest {
|
||||
projectID, clientID := createOIDCApplication(ctx, t, true, true)
|
||||
|
||||
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
|
||||
Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
|
||||
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
|
||||
Instance.CreateProjectUserGrant(t, ctx, projectID, user.GetUserId())
|
||||
|
||||
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
|
||||
},
|
||||
want: &oidc_pb.CreateCallbackResponse{
|
||||
CallbackUrl: `oidcintegrationtest:\/\/callback\?code=(.*)&state=state`,
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "usergrant to project grant and different resourceowner with project grant",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest {
|
||||
projectID, clientID := createOIDCApplication(ctx, t, true, true)
|
||||
|
||||
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
|
||||
projectGrantResp := Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
|
||||
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
|
||||
Instance.CreateProjectGrantUserGrant(ctx, orgResp.GetOrganizationId(), projectID, projectGrantResp.GetGrantId(), user.GetUserId())
|
||||
|
||||
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
|
||||
},
|
||||
want: &oidc_pb.CreateCallbackResponse{
|
||||
CallbackUrl: `oidcintegrationtest:\/\/callback\?code=(.*)&state=state`,
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no usergrant and different resourceowner",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest {
|
||||
_, clientID := createOIDCApplication(ctx, t, true, true)
|
||||
|
||||
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
|
||||
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
|
||||
|
||||
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "no usergrant and same resourceowner",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest {
|
||||
_, clientID := createOIDCApplication(ctx, t, true, true)
|
||||
user := Instance.CreateHumanUser(ctx)
|
||||
|
||||
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "usergrant and different resourceowner",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest {
|
||||
projectID, clientID := createOIDCApplication(ctx, t, true, true)
|
||||
|
||||
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
|
||||
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
|
||||
Instance.CreateProjectUserGrant(t, ctx, projectID, user.GetUserId())
|
||||
|
||||
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "usergrant and same resourceowner",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest {
|
||||
projectID, clientID := createOIDCApplication(ctx, t, true, true)
|
||||
user := Instance.CreateHumanUser(ctx)
|
||||
Instance.CreateProjectUserGrant(t, ctx, projectID, user.GetUserId())
|
||||
|
||||
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
|
||||
},
|
||||
want: &oidc_pb.CreateCallbackResponse{
|
||||
CallbackUrl: `oidcintegrationtest:\/\/callback\?code=(.*)&state=state`,
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "projectRoleCheck, usergrant and same resourceowner",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest {
|
||||
projectID, clientID := createOIDCApplication(ctx, t, true, false)
|
||||
user := Instance.CreateHumanUser(ctx)
|
||||
Instance.CreateProjectUserGrant(t, ctx, projectID, user.GetUserId())
|
||||
|
||||
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
|
||||
},
|
||||
want: &oidc_pb.CreateCallbackResponse{
|
||||
CallbackUrl: `oidcintegrationtest:\/\/callback\?code=(.*)&state=state`,
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "projectRoleCheck, no usergrant and same resourceowner",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest {
|
||||
_, clientID := createOIDCApplication(ctx, t, true, false)
|
||||
user := Instance.CreateHumanUser(ctx)
|
||||
|
||||
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "projectRoleCheck, usergrant and different resourceowner",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest {
|
||||
projectID, clientID := createOIDCApplication(ctx, t, true, false)
|
||||
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
|
||||
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
|
||||
Instance.CreateProjectUserGrant(t, ctx, projectID, user.GetUserId())
|
||||
|
||||
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
|
||||
},
|
||||
want: &oidc_pb.CreateCallbackResponse{
|
||||
CallbackUrl: `oidcintegrationtest:\/\/callback\?code=(.*)&state=state`,
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "projectRoleCheck, no usergrant and different resourceowner",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest {
|
||||
_, clientID := createOIDCApplication(ctx, t, true, false)
|
||||
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
|
||||
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
|
||||
|
||||
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "projectRoleCheck, usergrant on project grant and different resourceowner",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest {
|
||||
projectID, clientID := createOIDCApplication(ctx, t, true, false)
|
||||
|
||||
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
|
||||
projectGrantResp := Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
|
||||
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
|
||||
Instance.CreateProjectGrantUserGrant(ctx, orgResp.GetOrganizationId(), projectID, projectGrantResp.GetGrantId(), user.GetUserId())
|
||||
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
|
||||
},
|
||||
want: &oidc_pb.CreateCallbackResponse{
|
||||
CallbackUrl: `oidcintegrationtest:\/\/callback\?code=(.*)&state=state`,
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "projectRoleCheck, no usergrant on project grant and different resourceowner",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest {
|
||||
projectID, clientID := createOIDCApplication(ctx, t, true, false)
|
||||
|
||||
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
|
||||
Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
|
||||
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
|
||||
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "hasProjectCheck, same resourceowner",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest {
|
||||
user := Instance.CreateHumanUser(ctx)
|
||||
_, clientID := createOIDCApplication(ctx, t, false, true)
|
||||
|
||||
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
|
||||
},
|
||||
want: &oidc_pb.CreateCallbackResponse{
|
||||
CallbackUrl: `oidcintegrationtest:\/\/callback\?code=(.*)&state=state`,
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "hasProjectCheck, different resourceowner",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest {
|
||||
_, clientID := createOIDCApplication(ctx, t, false, true)
|
||||
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
|
||||
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
|
||||
|
||||
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "hasProjectCheck, different resourceowner with project grant",
|
||||
ctx: CTX,
|
||||
dep: func(ctx context.Context, t *testing.T) *oidc_pb.CreateCallbackRequest {
|
||||
projectID, clientID := createOIDCApplication(ctx, t, false, true)
|
||||
|
||||
orgResp := Instance.CreateOrganization(ctx, "oidc-permission-"+gofakeit.AppName(), gofakeit.Email())
|
||||
Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
|
||||
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
|
||||
|
||||
return createSessionAndAuthRequestForCallback(ctx, t, clientID, Instance.Users.Get(integration.UserTypeOrgOwner).ID, user.GetUserId())
|
||||
},
|
||||
want: &oidc_pb.CreateCallbackResponse{
|
||||
CallbackUrl: `oidcintegrationtest:\/\/callback\?code=(.*)&state=state`,
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req := tt.dep(IAMCTX, t)
|
||||
|
||||
got, err := Client.CreateCallback(tt.ctx, req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
integration.AssertDetails(t, tt.want, got)
|
||||
if tt.want != nil {
|
||||
assert.Regexp(t, regexp.MustCompile(tt.want.CallbackUrl), got.GetCallbackUrl())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func createSession(t *testing.T, ctx context.Context, userID string) *session.CreateSessionResponse {
|
||||
sessionResp, err := Instance.Client.SessionV2beta.CreateSession(ctx, &session.CreateSessionRequest{
|
||||
Checks: &session.Checks{
|
||||
User: &session.CheckUser{
|
||||
Search: &session.CheckUser_UserId{
|
||||
UserId: userID,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return sessionResp
|
||||
}
|
||||
|
||||
func createSessionAndAuthRequestForCallback(ctx context.Context, t *testing.T, clientID, loginClient, userID string) *oidc_pb.CreateCallbackRequest {
|
||||
_, authRequestID, err := Instance.CreateOIDCAuthRequest(ctx, clientID, loginClient, redirectURI)
|
||||
require.NoError(t, err)
|
||||
sessionResp := createSession(t, ctx, userID)
|
||||
return &oidc_pb.CreateCallbackRequest{
|
||||
AuthRequestId: authRequestID,
|
||||
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
|
||||
Session: &oidc_pb.Session{
|
||||
SessionId: sessionResp.GetSessionId(),
|
||||
SessionToken: sessionResp.GetSessionToken(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func createOIDCApplication(ctx context.Context, t *testing.T, projectRoleCheck, hasProjectCheck bool) (string, string) {
|
||||
project, err := Instance.CreateProjectWithPermissionCheck(ctx, projectRoleCheck, hasProjectCheck)
|
||||
require.NoError(t, err)
|
||||
clientV2, err := Instance.CreateOIDCClientLoginVersion(ctx, redirectURI, logoutRedirectURI, project.GetId(), app.OIDCAppType_OIDC_APP_TYPE_NATIVE, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE, false, loginV2)
|
||||
require.NoError(t, err)
|
||||
return project.GetId(), clientV2.GetClientId()
|
||||
}
|
||||
|
@ -0,0 +1,44 @@
|
||||
//go:build integration
|
||||
|
||||
package oidc_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/app"
|
||||
oidc_pb "github.com/zitadel/zitadel/pkg/grpc/oidc/v2beta"
|
||||
)
|
||||
|
||||
var (
|
||||
CTX context.Context
|
||||
CTXLoginClient context.Context
|
||||
IAMCTX context.Context
|
||||
Instance *integration.Instance
|
||||
Client oidc_pb.OIDCServiceClient
|
||||
loginV2 = &app.LoginVersion{Version: &app.LoginVersion_LoginV2{LoginV2: &app.LoginV2{BaseUri: nil}}}
|
||||
)
|
||||
|
||||
const (
|
||||
redirectURI = "oidcintegrationtest://callback"
|
||||
redirectURIImplicit = "http://localhost:9999/callback"
|
||||
logoutRedirectURI = "oidcintegrationtest://logged-out"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(func() int {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
Instance = integration.NewInstance(ctx)
|
||||
Client = Instance.Client.OIDCv2beta
|
||||
|
||||
IAMCTX = Instance.WithAuthorization(ctx, integration.UserTypeIAMOwner)
|
||||
CTX = Instance.WithAuthorization(ctx, integration.UserTypeOrgOwner)
|
||||
CTXLoginClient = Instance.WithAuthorization(ctx, integration.UserTypeLogin)
|
||||
return m.Run()
|
||||
}())
|
||||
}
|
@ -100,8 +100,22 @@ func (s *Server) failAuthRequest(ctx context.Context, authRequestID string, ae *
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) checkPermission(ctx context.Context, clientID string, userID string) error {
|
||||
permission, err := s.query.CheckProjectPermissionByClientID(ctx, clientID, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !permission.HasProjectChecked {
|
||||
return zerrors.ThrowPermissionDenied(nil, "OIDC-BakSFPjbfN", "Errors.User.ProjectRequired")
|
||||
}
|
||||
if !permission.ProjectRoleChecked {
|
||||
return zerrors.ThrowPermissionDenied(nil, "OIDC-EP688AF2jA", "Errors.User.GrantRequired")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) linkSessionToAuthRequest(ctx context.Context, authRequestID string, session *oidc_pb.Session) (*oidc_pb.CreateCallbackResponse, error) {
|
||||
details, aar, err := s.command.LinkSessionToAuthRequest(ctx, authRequestID, session.GetSessionId(), session.GetSessionToken(), true)
|
||||
details, aar, err := s.command.LinkSessionToAuthRequest(ctx, authRequestID, session.GetSessionId(), session.GetSessionToken(), true, s.checkPermission)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -43,6 +43,7 @@ func TestMain(m *testing.M) {
|
||||
|
||||
func TestServer_AddOrganization(t *testing.T) {
|
||||
idpResp := Instance.AddGenericOAuthProvider(CTX, Instance.DefaultOrg.Id)
|
||||
userId := "userID"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@ -81,7 +82,7 @@ func TestServer_AddOrganization(t *testing.T) {
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "admin with init",
|
||||
name: "admin with init with userID passed for Human admin",
|
||||
ctx: CTX,
|
||||
req: &org.AddOrganizationRequest{
|
||||
Name: gofakeit.AppName(),
|
||||
@ -89,6 +90,7 @@ func TestServer_AddOrganization(t *testing.T) {
|
||||
{
|
||||
UserType: &org.AddOrganizationRequest_Admin_Human{
|
||||
Human: &user.AddHumanUserRequest{
|
||||
UserId: &userId,
|
||||
Profile: &user.SetHumanProfile{
|
||||
GivenName: "firstname",
|
||||
FamilyName: "lastname",
|
||||
@ -108,7 +110,7 @@ func TestServer_AddOrganization(t *testing.T) {
|
||||
OrganizationId: integration.NotEmpty,
|
||||
CreatedAdmins: []*org.AddOrganizationResponse_CreatedAdmin{
|
||||
{
|
||||
UserId: integration.NotEmpty,
|
||||
UserId: userId,
|
||||
EmailCode: gu.Ptr(integration.NotEmpty),
|
||||
PhoneCode: nil,
|
||||
},
|
||||
@ -140,7 +142,7 @@ func TestServer_AddOrganization(t *testing.T) {
|
||||
IdpLinks: []*user.IDPLink{
|
||||
{
|
||||
IdpId: idpResp.Id,
|
||||
UserId: "userID",
|
||||
UserId: userId,
|
||||
UserName: "username",
|
||||
},
|
||||
},
|
||||
|
@ -57,6 +57,7 @@ func addOrganizationRequestAdminToCommand(admin *org.AddOrganizationRequest_Admi
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &command.OrgSetupAdmin{
|
||||
Human: human,
|
||||
Roles: admin.GetRoles(),
|
||||
|
@ -5,13 +5,13 @@ package saml_test
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/crewjam/saml"
|
||||
"github.com/crewjam/saml/samlsp"
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -19,80 +19,48 @@ import (
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
||||
oidc_pb "github.com/zitadel/zitadel/pkg/grpc/oidc/v2"
|
||||
saml_pb "github.com/zitadel/zitadel/pkg/grpc/saml/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/session/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
CTX context.Context
|
||||
Instance *integration.Instance
|
||||
Client saml_pb.SAMLServiceClient
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(func() int {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
Instance = integration.NewInstance(ctx)
|
||||
Client = Instance.Client.SAMLv2
|
||||
|
||||
CTX = Instance.WithAuthorization(ctx, integration.UserTypeOrgOwner)
|
||||
return m.Run()
|
||||
}())
|
||||
}
|
||||
|
||||
func TestServer_GetAuthRequest(t *testing.T) {
|
||||
rootURL := "https://sp.example.com"
|
||||
func TestServer_GetSAMLRequest(t *testing.T) {
|
||||
idpMetadata, err := Instance.GetSAMLIDPMetadata()
|
||||
require.NoError(t, err)
|
||||
spMiddlewareRedirect, err := integration.CreateSAMLSP(rootURL, idpMetadata, saml.HTTPRedirectBinding)
|
||||
require.NoError(t, err)
|
||||
spMiddlewarePost, err := integration.CreateSAMLSP(rootURL, idpMetadata, saml.HTTPPostBinding)
|
||||
require.NoError(t, err)
|
||||
|
||||
acsRedirect := idpMetadata.IDPSSODescriptors[0].SingleSignOnServices[0]
|
||||
acsPost := idpMetadata.IDPSSODescriptors[0].SingleSignOnServices[1]
|
||||
|
||||
project, err := Instance.CreateProject(CTX)
|
||||
require.NoError(t, err)
|
||||
_, err = Instance.CreateSAMLClient(CTX, project.GetId(), spMiddlewareRedirect)
|
||||
require.NoError(t, err)
|
||||
_, err = Instance.CreateSAMLClient(CTX, project.GetId(), spMiddlewarePost)
|
||||
require.NoError(t, err)
|
||||
|
||||
now := time.Now()
|
||||
_, _, spMiddlewareRedirect := createSAMLApplication(CTX, t, idpMetadata, saml.HTTPRedirectBinding, false, false)
|
||||
_, _, spMiddlewarePost := createSAMLApplication(CTX, t, idpMetadata, saml.HTTPPostBinding, false, false)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
dep func() (string, error)
|
||||
want *oidc_pb.GetAuthRequestResponse
|
||||
dep func() (time.Time, string, error)
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Not found",
|
||||
dep: func() (string, error) {
|
||||
return "123", nil
|
||||
dep: func() (time.Time, string, error) {
|
||||
return time.Time{}, "123", nil
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "success, redirect binding",
|
||||
dep: func() (string, error) {
|
||||
dep: func() (time.Time, string, error) {
|
||||
return Instance.CreateSAMLAuthRequest(spMiddlewareRedirect, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, gofakeit.BitcoinAddress(), saml.HTTPRedirectBinding)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "success, post binding",
|
||||
dep: func() (string, error) {
|
||||
dep: func() (time.Time, string, error) {
|
||||
return Instance.CreateSAMLAuthRequest(spMiddlewarePost, Instance.Users[integration.UserTypeOrgOwner].ID, acsPost, gofakeit.BitcoinAddress(), saml.HTTPPostBinding)
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
authRequestID, err := tt.dep()
|
||||
creationTime, authRequestID, err := tt.dep()
|
||||
require.NoError(t, err)
|
||||
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
|
||||
@ -108,7 +76,7 @@ func TestServer_GetAuthRequest(t *testing.T) {
|
||||
authRequest := got.GetSamlRequest()
|
||||
assert.NotNil(ttt, authRequest)
|
||||
assert.Equal(ttt, authRequestID, authRequest.GetId())
|
||||
assert.WithinRange(ttt, authRequest.GetCreationDate().AsTime(), now.Add(-time.Second), now.Add(time.Second))
|
||||
assert.WithinRange(ttt, authRequest.GetCreationDate().AsTime(), creationTime.Add(-time.Second), creationTime.Add(time.Second))
|
||||
}, retryDuration, tick, "timeout waiting for expected saml request result")
|
||||
})
|
||||
}
|
||||
@ -117,33 +85,12 @@ func TestServer_GetAuthRequest(t *testing.T) {
|
||||
func TestServer_CreateResponse(t *testing.T) {
|
||||
idpMetadata, err := Instance.GetSAMLIDPMetadata()
|
||||
require.NoError(t, err)
|
||||
rootURLRedirect := "spredirect.example.com"
|
||||
spMiddlewareRedirect, err := integration.CreateSAMLSP("https://"+rootURLRedirect, idpMetadata, saml.HTTPRedirectBinding)
|
||||
require.NoError(t, err)
|
||||
rootURLPost := "sppost.example.com"
|
||||
spMiddlewarePost, err := integration.CreateSAMLSP("https://"+rootURLPost, idpMetadata, saml.HTTPPostBinding)
|
||||
require.NoError(t, err)
|
||||
|
||||
acsRedirect := idpMetadata.IDPSSODescriptors[0].SingleSignOnServices[0]
|
||||
acsPost := idpMetadata.IDPSSODescriptors[0].SingleSignOnServices[1]
|
||||
|
||||
project, err := Instance.CreateProject(CTX)
|
||||
require.NoError(t, err)
|
||||
_, err = Instance.CreateSAMLClient(CTX, project.GetId(), spMiddlewareRedirect)
|
||||
require.NoError(t, err)
|
||||
_, err = Instance.CreateSAMLClient(CTX, project.GetId(), spMiddlewarePost)
|
||||
require.NoError(t, err)
|
||||
|
||||
sessionResp, err := Instance.Client.SessionV2.CreateSession(CTX, &session.CreateSessionRequest{
|
||||
Checks: &session.Checks{
|
||||
User: &session.CheckUser{
|
||||
Search: &session.CheckUser_UserId{
|
||||
UserId: Instance.Users[integration.UserTypeOrgOwner].ID,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, rootURLPost, spMiddlewarePost := createSAMLApplication(CTX, t, idpMetadata, saml.HTTPPostBinding, false, false)
|
||||
_, rootURLRedirect, spMiddlewareRedirect := createSAMLApplication(CTX, t, idpMetadata, saml.HTTPRedirectBinding, false, false)
|
||||
sessionResp := createSession(CTX, t, Instance.Users[integration.UserTypeOrgOwner].ID)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@ -170,7 +117,7 @@ func TestServer_CreateResponse(t *testing.T) {
|
||||
name: "session not found",
|
||||
req: &saml_pb.CreateResponseRequest{
|
||||
SamlRequestId: func() string {
|
||||
authRequestID, err := Instance.CreateSAMLAuthRequest(spMiddlewareRedirect, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, gofakeit.BitcoinAddress(), saml.HTTPRedirectBinding)
|
||||
_, authRequestID, err := Instance.CreateSAMLAuthRequest(spMiddlewareRedirect, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, gofakeit.BitcoinAddress(), saml.HTTPRedirectBinding)
|
||||
require.NoError(t, err)
|
||||
return authRequestID
|
||||
}(),
|
||||
@ -187,7 +134,7 @@ func TestServer_CreateResponse(t *testing.T) {
|
||||
name: "session token invalid",
|
||||
req: &saml_pb.CreateResponseRequest{
|
||||
SamlRequestId: func() string {
|
||||
authRequestID, err := Instance.CreateSAMLAuthRequest(spMiddlewareRedirect, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, gofakeit.BitcoinAddress(), saml.HTTPRedirectBinding)
|
||||
_, authRequestID, err := Instance.CreateSAMLAuthRequest(spMiddlewareRedirect, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, gofakeit.BitcoinAddress(), saml.HTTPRedirectBinding)
|
||||
require.NoError(t, err)
|
||||
return authRequestID
|
||||
}(),
|
||||
@ -204,7 +151,7 @@ func TestServer_CreateResponse(t *testing.T) {
|
||||
name: "fail callback, post",
|
||||
req: &saml_pb.CreateResponseRequest{
|
||||
SamlRequestId: func() string {
|
||||
authRequestID, err := Instance.CreateSAMLAuthRequest(spMiddlewarePost, Instance.Users[integration.UserTypeOrgOwner].ID, acsPost, gofakeit.BitcoinAddress(), saml.HTTPPostBinding)
|
||||
_, authRequestID, err := Instance.CreateSAMLAuthRequest(spMiddlewarePost, Instance.Users[integration.UserTypeOrgOwner].ID, acsPost, gofakeit.BitcoinAddress(), saml.HTTPPostBinding)
|
||||
require.NoError(t, err)
|
||||
return authRequestID
|
||||
}(),
|
||||
@ -232,7 +179,7 @@ func TestServer_CreateResponse(t *testing.T) {
|
||||
name: "fail callback, post, already failed",
|
||||
req: &saml_pb.CreateResponseRequest{
|
||||
SamlRequestId: func() string {
|
||||
authRequestID, err := Instance.CreateSAMLAuthRequest(spMiddlewarePost, Instance.Users[integration.UserTypeOrgOwner].ID, acsPost, gofakeit.BitcoinAddress(), saml.HTTPPostBinding)
|
||||
_, authRequestID, err := Instance.CreateSAMLAuthRequest(spMiddlewarePost, Instance.Users[integration.UserTypeOrgOwner].ID, acsPost, gofakeit.BitcoinAddress(), saml.HTTPPostBinding)
|
||||
require.NoError(t, err)
|
||||
Instance.FailSAMLAuthRequest(CTX, authRequestID, saml_pb.ErrorReason_ERROR_REASON_AUTH_N_FAILED)
|
||||
return authRequestID
|
||||
@ -250,7 +197,7 @@ func TestServer_CreateResponse(t *testing.T) {
|
||||
name: "fail callback, redirect",
|
||||
req: &saml_pb.CreateResponseRequest{
|
||||
SamlRequestId: func() string {
|
||||
authRequestID, err := Instance.CreateSAMLAuthRequest(spMiddlewareRedirect, Instance.Users[integration.UserTypeOrgOwner].ID, acsPost, gofakeit.BitcoinAddress(), saml.HTTPPostBinding)
|
||||
_, authRequestID, err := Instance.CreateSAMLAuthRequest(spMiddlewareRedirect, Instance.Users[integration.UserTypeOrgOwner].ID, acsPost, gofakeit.BitcoinAddress(), saml.HTTPPostBinding)
|
||||
require.NoError(t, err)
|
||||
return authRequestID
|
||||
}(),
|
||||
@ -275,7 +222,7 @@ func TestServer_CreateResponse(t *testing.T) {
|
||||
name: "callback, redirect",
|
||||
req: &saml_pb.CreateResponseRequest{
|
||||
SamlRequestId: func() string {
|
||||
authRequestID, err := Instance.CreateSAMLAuthRequest(spMiddlewareRedirect, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, gofakeit.BitcoinAddress(), saml.HTTPRedirectBinding)
|
||||
_, authRequestID, err := Instance.CreateSAMLAuthRequest(spMiddlewareRedirect, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, gofakeit.BitcoinAddress(), saml.HTTPRedirectBinding)
|
||||
require.NoError(t, err)
|
||||
return authRequestID
|
||||
}(),
|
||||
@ -300,7 +247,7 @@ func TestServer_CreateResponse(t *testing.T) {
|
||||
name: "callback, post",
|
||||
req: &saml_pb.CreateResponseRequest{
|
||||
SamlRequestId: func() string {
|
||||
authRequestID, err := Instance.CreateSAMLAuthRequest(spMiddlewarePost, Instance.Users[integration.UserTypeOrgOwner].ID, acsPost, gofakeit.BitcoinAddress(), saml.HTTPPostBinding)
|
||||
_, authRequestID, err := Instance.CreateSAMLAuthRequest(spMiddlewarePost, Instance.Users[integration.UserTypeOrgOwner].ID, acsPost, gofakeit.BitcoinAddress(), saml.HTTPPostBinding)
|
||||
require.NoError(t, err)
|
||||
return authRequestID
|
||||
}(),
|
||||
@ -328,7 +275,7 @@ func TestServer_CreateResponse(t *testing.T) {
|
||||
name: "callback, post",
|
||||
req: &saml_pb.CreateResponseRequest{
|
||||
SamlRequestId: func() string {
|
||||
authRequestID, err := Instance.CreateSAMLAuthRequest(spMiddlewarePost, Instance.Users[integration.UserTypeOrgOwner].ID, acsPost, gofakeit.BitcoinAddress(), saml.HTTPPostBinding)
|
||||
_, authRequestID, err := Instance.CreateSAMLAuthRequest(spMiddlewarePost, Instance.Users[integration.UserTypeOrgOwner].ID, acsPost, gofakeit.BitcoinAddress(), saml.HTTPPostBinding)
|
||||
require.NoError(t, err)
|
||||
Instance.SuccessfulSAMLAuthRequest(CTX, Instance.Users[integration.UserTypeOrgOwner].ID, authRequestID)
|
||||
return authRequestID
|
||||
@ -365,3 +312,338 @@ func TestServer_CreateResponse(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_CreateResponse_Permission(t *testing.T) {
|
||||
idpMetadata, err := Instance.GetSAMLIDPMetadata()
|
||||
require.NoError(t, err)
|
||||
acsRedirect := idpMetadata.IDPSSODescriptors[0].SingleSignOnServices[0]
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
dep func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest
|
||||
want *saml_pb.CreateResponseResponse
|
||||
wantURL *url.URL
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "usergrant to project and different resourceowner with different project grant",
|
||||
dep: func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest {
|
||||
projectID, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, true, true)
|
||||
projectID2, _, _ := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, true, true)
|
||||
|
||||
orgResp := Instance.CreateOrganization(ctx, "saml-permission-"+gofakeit.AppName(), gofakeit.Email())
|
||||
Instance.CreateProjectGrant(ctx, projectID2, orgResp.GetOrganizationId())
|
||||
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
|
||||
Instance.CreateProjectUserGrant(t, ctx, projectID, user.GetUserId())
|
||||
|
||||
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "usergrant to project and different resourceowner with project grant",
|
||||
dep: func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest {
|
||||
projectID, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, true, true)
|
||||
|
||||
orgResp := Instance.CreateOrganization(ctx, "saml-permission-"+gofakeit.AppName(), gofakeit.Email())
|
||||
Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
|
||||
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
|
||||
Instance.CreateProjectUserGrant(t, ctx, projectID, user.GetUserId())
|
||||
|
||||
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
|
||||
},
|
||||
want: &saml_pb.CreateResponseResponse{
|
||||
Url: `https:\/\/(.*)\/saml\/acs\?RelayState=(.*)&SAMLResponse=(.*)&SigAlg=(.*)&Signature=(.*)`,
|
||||
Binding: &saml_pb.CreateResponseResponse_Redirect{Redirect: &saml_pb.RedirectResponse{}},
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Instance.ID(),
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "usergrant to project grant and different resourceowner with project grant",
|
||||
dep: func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest {
|
||||
projectID, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, true, true)
|
||||
|
||||
orgResp := Instance.CreateOrganization(ctx, "saml-permission-"+gofakeit.AppName(), gofakeit.Email())
|
||||
projectGrantResp := Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
|
||||
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
|
||||
Instance.CreateProjectGrantUserGrant(ctx, orgResp.GetOrganizationId(), projectID, projectGrantResp.GetGrantId(), user.GetUserId())
|
||||
|
||||
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
|
||||
},
|
||||
want: &saml_pb.CreateResponseResponse{
|
||||
Url: `https:\/\/(.*)\/saml\/acs\?RelayState=(.*)&SAMLResponse=(.*)&SigAlg=(.*)&Signature=(.*)`,
|
||||
Binding: &saml_pb.CreateResponseResponse_Redirect{Redirect: &saml_pb.RedirectResponse{}},
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Instance.ID(),
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "no usergrant and different resourceowner",
|
||||
dep: func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest {
|
||||
_, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, true, true)
|
||||
|
||||
orgResp := Instance.CreateOrganization(ctx, "saml-permisison-"+gofakeit.AppName(), gofakeit.Email())
|
||||
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
|
||||
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "no usergrant and same resourceowner",
|
||||
dep: func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest {
|
||||
_, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, true, true)
|
||||
user := Instance.CreateHumanUser(ctx)
|
||||
|
||||
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "usergrant and different resourceowner",
|
||||
dep: func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest {
|
||||
projectID, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, true, true)
|
||||
|
||||
orgResp := Instance.CreateOrganization(ctx, "saml-permisison-"+gofakeit.AppName(), gofakeit.Email())
|
||||
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
|
||||
Instance.CreateProjectUserGrant(t, ctx, projectID, user.GetUserId())
|
||||
|
||||
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "usergrant and same resourceowner",
|
||||
dep: func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest {
|
||||
projectID, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, true, true)
|
||||
|
||||
user := Instance.CreateHumanUser(ctx)
|
||||
Instance.CreateProjectUserGrant(t, ctx, projectID, user.GetUserId())
|
||||
|
||||
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
|
||||
},
|
||||
want: &saml_pb.CreateResponseResponse{
|
||||
Url: `https:\/\/(.*)\/saml\/acs\?RelayState=(.*)&SAMLResponse=(.*)&SigAlg=(.*)&Signature=(.*)`,
|
||||
Binding: &saml_pb.CreateResponseResponse_Redirect{Redirect: &saml_pb.RedirectResponse{}},
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "projectRoleCheck, usergrant and same resourceowner",
|
||||
dep: func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest {
|
||||
projectID, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, true, false)
|
||||
|
||||
user := Instance.CreateHumanUser(ctx)
|
||||
Instance.CreateProjectUserGrant(t, ctx, projectID, user.GetUserId())
|
||||
|
||||
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
|
||||
},
|
||||
want: &saml_pb.CreateResponseResponse{
|
||||
Url: `https:\/\/(.*)\/saml\/acs\?RelayState=(.*)&SAMLResponse=(.*)&SigAlg=(.*)&Signature=(.*)`,
|
||||
Binding: &saml_pb.CreateResponseResponse_Redirect{Redirect: &saml_pb.RedirectResponse{}},
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "projectRoleCheck, no usergrant and same resourceowner",
|
||||
dep: func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest {
|
||||
_, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, true, false)
|
||||
user := Instance.CreateHumanUser(ctx)
|
||||
|
||||
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "projectRoleCheck, usergrant and different resourceowner",
|
||||
dep: func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest {
|
||||
projectID, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, true, false)
|
||||
orgResp := Instance.CreateOrganization(ctx, "saml-permisison-"+gofakeit.AppName(), gofakeit.Email())
|
||||
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
|
||||
Instance.CreateProjectUserGrant(t, ctx, projectID, user.GetUserId())
|
||||
|
||||
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
|
||||
},
|
||||
want: &saml_pb.CreateResponseResponse{
|
||||
Url: `https:\/\/(.*)\/saml\/acs\?RelayState=(.*)&SAMLResponse=(.*)&SigAlg=(.*)&Signature=(.*)`,
|
||||
Binding: &saml_pb.CreateResponseResponse_Redirect{Redirect: &saml_pb.RedirectResponse{}},
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "projectRoleCheck, no usergrant and different resourceowner",
|
||||
dep: func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest {
|
||||
_, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, true, false)
|
||||
orgResp := Instance.CreateOrganization(ctx, "saml-permisison-"+gofakeit.AppName(), gofakeit.Email())
|
||||
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
|
||||
|
||||
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "projectRoleCheck, usergrant on project grant and different resourceowner",
|
||||
dep: func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest {
|
||||
projectID, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, true, false)
|
||||
|
||||
orgResp := Instance.CreateOrganization(ctx, "saml-permissison-"+gofakeit.AppName(), gofakeit.Email())
|
||||
projectGrantResp := Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
|
||||
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
|
||||
Instance.CreateProjectGrantUserGrant(ctx, orgResp.GetOrganizationId(), projectID, projectGrantResp.GetGrantId(), user.GetUserId())
|
||||
|
||||
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
|
||||
},
|
||||
want: &saml_pb.CreateResponseResponse{
|
||||
Url: `https:\/\/(.*)\/saml\/acs\?RelayState=(.*)&SAMLResponse=(.*)&SigAlg=(.*)&Signature=(.*)`,
|
||||
Binding: &saml_pb.CreateResponseResponse_Redirect{Redirect: &saml_pb.RedirectResponse{}},
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "projectRoleCheck, no usergrant on project grant and different resourceowner",
|
||||
dep: func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest {
|
||||
projectID, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, true, false)
|
||||
|
||||
orgResp := Instance.CreateOrganization(ctx, "saml-permissison-"+gofakeit.AppName(), gofakeit.Email())
|
||||
Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
|
||||
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
|
||||
|
||||
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "hasProjectCheck, same resourceowner",
|
||||
dep: func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest {
|
||||
_, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, false, true)
|
||||
user := Instance.CreateHumanUser(ctx)
|
||||
|
||||
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
|
||||
},
|
||||
want: &saml_pb.CreateResponseResponse{
|
||||
Url: `https:\/\/(.*)\/saml\/acs\?RelayState=(.*)&SAMLResponse=(.*)&SigAlg=(.*)&Signature=(.*)`,
|
||||
Binding: &saml_pb.CreateResponseResponse_Redirect{Redirect: &saml_pb.RedirectResponse{}},
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "hasProjectCheck, different resourceowner",
|
||||
dep: func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest {
|
||||
_, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, false, true)
|
||||
orgResp := Instance.CreateOrganization(ctx, "saml-permisison-"+gofakeit.AppName(), gofakeit.Email())
|
||||
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
|
||||
|
||||
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "hasProjectCheck, different resourceowner with project grant",
|
||||
dep: func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest {
|
||||
projectID, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, false, true)
|
||||
orgResp := Instance.CreateOrganization(ctx, "saml-permissison-"+gofakeit.AppName(), gofakeit.Email())
|
||||
Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
|
||||
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
|
||||
|
||||
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
|
||||
},
|
||||
want: &saml_pb.CreateResponseResponse{
|
||||
Url: `https:\/\/(.*)\/saml\/acs\?RelayState=(.*)&SAMLResponse=(.*)&SigAlg=(.*)&Signature=(.*)`,
|
||||
Binding: &saml_pb.CreateResponseResponse_Redirect{Redirect: &saml_pb.RedirectResponse{}},
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req := tt.dep(IAMCTX, t)
|
||||
|
||||
got, err := Client.CreateResponse(CTX, req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
integration.AssertDetails(t, tt.want, got)
|
||||
if tt.want != nil {
|
||||
assert.Regexp(t, regexp.MustCompile(tt.want.Url), got.GetUrl())
|
||||
if tt.want.GetPost() != nil {
|
||||
assert.NotEmpty(t, got.GetPost().GetRelayState())
|
||||
assert.NotEmpty(t, got.GetPost().GetSamlResponse())
|
||||
}
|
||||
if tt.want.GetRedirect() != nil {
|
||||
assert.NotNil(t, got.GetRedirect())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func createSession(ctx context.Context, t *testing.T, userID string) *session.CreateSessionResponse {
|
||||
sessionResp, err := Instance.Client.SessionV2.CreateSession(ctx, &session.CreateSessionRequest{
|
||||
Checks: &session.Checks{
|
||||
User: &session.CheckUser{
|
||||
Search: &session.CheckUser_UserId{
|
||||
UserId: userID,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return sessionResp
|
||||
}
|
||||
|
||||
func createSessionAndSmlRequestForCallback(ctx context.Context, t *testing.T, sp *samlsp.Middleware, loginClient string, acsRedirect saml.Endpoint, userID, binding string) *saml_pb.CreateResponseRequest {
|
||||
_, authRequestID, err := Instance.CreateSAMLAuthRequest(sp, loginClient, acsRedirect, gofakeit.BitcoinAddress(), binding)
|
||||
require.NoError(t, err)
|
||||
sessionResp := createSession(ctx, t, userID)
|
||||
return &saml_pb.CreateResponseRequest{
|
||||
SamlRequestId: authRequestID,
|
||||
ResponseKind: &saml_pb.CreateResponseRequest_Session{
|
||||
Session: &saml_pb.Session{
|
||||
SessionId: sessionResp.GetSessionId(),
|
||||
SessionToken: sessionResp.GetSessionToken(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func createSAMLSP(t *testing.T, idpMetadata *saml.EntityDescriptor, binding string) (string, *samlsp.Middleware) {
|
||||
rootURL := "example." + gofakeit.DomainName()
|
||||
spMiddleware, err := integration.CreateSAMLSP("https://"+rootURL, idpMetadata, binding)
|
||||
require.NoError(t, err)
|
||||
return rootURL, spMiddleware
|
||||
}
|
||||
|
||||
func createSAMLApplication(ctx context.Context, t *testing.T, idpMetadata *saml.EntityDescriptor, binding string, projectRoleCheck, hasProjectCheck bool) (string, string, *samlsp.Middleware) {
|
||||
project, err := Instance.CreateProjectWithPermissionCheck(ctx, projectRoleCheck, hasProjectCheck)
|
||||
require.NoError(t, err)
|
||||
rootURL, sp := createSAMLSP(t, idpMetadata, binding)
|
||||
_, err = Instance.CreateSAMLClient(ctx, project.GetId(), sp)
|
||||
require.NoError(t, err)
|
||||
return project.GetId(), rootURL, sp
|
||||
}
|
||||
|
34
internal/api/grpc/saml/v2/integration/server_test.go
Normal file
34
internal/api/grpc/saml/v2/integration/server_test.go
Normal file
@ -0,0 +1,34 @@
|
||||
//go:build integration
|
||||
|
||||
package saml_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
saml_pb "github.com/zitadel/zitadel/pkg/grpc/saml/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
CTX context.Context
|
||||
IAMCTX context.Context
|
||||
Instance *integration.Instance
|
||||
Client saml_pb.SAMLServiceClient
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(func() int {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
Instance = integration.NewInstance(ctx)
|
||||
Client = Instance.Client.SAMLv2
|
||||
|
||||
IAMCTX = Instance.WithAuthorization(ctx, integration.UserTypeIAMOwner)
|
||||
CTX = Instance.WithAuthorization(ctx, integration.UserTypeOrgOwner)
|
||||
return m.Run()
|
||||
}())
|
||||
}
|
@ -14,7 +14,7 @@ import (
|
||||
saml_pb "github.com/zitadel/zitadel/pkg/grpc/saml/v2"
|
||||
)
|
||||
|
||||
func (s *Server) GetAuthRequest(ctx context.Context, req *saml_pb.GetSAMLRequestRequest) (*saml_pb.GetSAMLRequestResponse, error) {
|
||||
func (s *Server) GetSAMLRequest(ctx context.Context, req *saml_pb.GetSAMLRequestRequest) (*saml_pb.GetSAMLRequestResponse, error) {
|
||||
authRequest, err := s.query.SamlRequestByID(ctx, true, req.GetSamlRequestId(), true)
|
||||
if err != nil {
|
||||
logging.WithError(err).Error("query samlRequest by ID")
|
||||
@ -56,8 +56,22 @@ func (s *Server) failSAMLRequest(ctx context.Context, samlRequestID string, ae *
|
||||
return createCallbackResponseFromBinding(details, url, body, authReq.RelayState), nil
|
||||
}
|
||||
|
||||
func (s *Server) checkPermission(ctx context.Context, issuer string, userID string) error {
|
||||
permission, err := s.query.CheckProjectPermissionByEntityID(ctx, issuer, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !permission.HasProjectChecked {
|
||||
return zerrors.ThrowPermissionDenied(nil, "SAML-foSyH49RvL", "Errors.User.ProjectRequired")
|
||||
}
|
||||
if !permission.ProjectRoleChecked {
|
||||
return zerrors.ThrowPermissionDenied(nil, "SAML-foSyH49RvL", "Errors.User.GrantRequired")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) linkSessionToSAMLRequest(ctx context.Context, samlRequestID string, session *saml_pb.Session) (*saml_pb.CreateResponseResponse, error) {
|
||||
details, aar, err := s.command.LinkSessionToSAMLRequest(ctx, samlRequestID, session.GetSessionId(), session.GetSessionToken(), true)
|
||||
details, aar, err := s.command.LinkSessionToSAMLRequest(ctx, samlRequestID, session.GetSessionId(), session.GetSessionToken(), true, s.checkPermission)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -695,6 +695,12 @@ func TestServer_ListSessions(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
// expected count of sessions is not equal to created dependencies
|
||||
if !assert.Len(ttt, tt.want.Sessions, len(infos)) {
|
||||
return
|
||||
}
|
||||
|
||||
// expected count of sessions is not equal to received sessions
|
||||
if !assert.Equal(ttt, got.Details.TotalResult, tt.want.Details.TotalResult) || !assert.Len(ttt, got.Sessions, len(tt.want.Sessions)) {
|
||||
return
|
||||
}
|
||||
@ -705,8 +711,17 @@ func TestServer_ListSessions(t *testing.T) {
|
||||
tt.want.Sessions[i].CreationDate = infos[i].Details.GetChangeDate()
|
||||
tt.want.Sessions[i].ChangeDate = infos[i].Details.GetChangeDate()
|
||||
|
||||
verifySession(ttt, got.Sessions[i], tt.want.Sessions[i], time.Minute, tt.wantExpirationWindow, infos[i].UserID, tt.wantFactors...)
|
||||
// only check for contents of the session, not sorting for now
|
||||
found := false
|
||||
for _, session := range got.Sessions {
|
||||
if session.Id == infos[i].ID {
|
||||
verifySession(ttt, session, tt.want.Sessions[i], time.Minute, tt.wantExpirationWindow, infos[i].UserID, tt.wantFactors...)
|
||||
found = true
|
||||
}
|
||||
}
|
||||
assert.True(t, found)
|
||||
}
|
||||
|
||||
integration.AssertListDetails(ttt, tt.want, got)
|
||||
}, retryDuration, tick)
|
||||
})
|
||||
|
@ -493,6 +493,12 @@ func TestServer_ListSessions(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
// expected count of sessions is not equal to created dependencies
|
||||
if !assert.Len(ttt, tt.want.Sessions, len(infos)) {
|
||||
return
|
||||
}
|
||||
|
||||
// expected count of sessions is not equal to received sessions
|
||||
if !assert.Equal(ttt, got.Details.TotalResult, tt.want.Details.TotalResult) || !assert.Len(ttt, got.Sessions, len(tt.want.Sessions)) {
|
||||
return
|
||||
}
|
||||
@ -503,8 +509,17 @@ func TestServer_ListSessions(t *testing.T) {
|
||||
tt.want.Sessions[i].CreationDate = infos[i].Details.GetChangeDate()
|
||||
tt.want.Sessions[i].ChangeDate = infos[i].Details.GetChangeDate()
|
||||
|
||||
verifySession(ttt, got.Sessions[i], tt.want.Sessions[i], time.Minute, tt.wantExpirationWindow, infos[i].UserID, tt.wantFactors...)
|
||||
// only check for contents of the session, not sorting for now
|
||||
found := false
|
||||
for _, session := range got.Sessions {
|
||||
if session.Id == infos[i].ID {
|
||||
verifySession(ttt, session, tt.want.Sessions[i], time.Minute, tt.wantExpirationWindow, infos[i].UserID, tt.wantFactors...)
|
||||
found = true
|
||||
}
|
||||
}
|
||||
assert.True(t, found)
|
||||
}
|
||||
|
||||
integration.AssertListDetails(ttt, tt.want, got)
|
||||
}, retryDuration, tick)
|
||||
})
|
||||
|
@ -46,6 +46,11 @@ func (o *OPStorage) CreateAuthRequest(ctx context.Context, req *oidc.AuthRequest
|
||||
headers, _ := http_utils.HeadersFromCtx(ctx)
|
||||
loginClient := headers.Get(LoginClientHeader)
|
||||
|
||||
// for backwards compatibility we'll use the new login if the header is set (no matter the other configs)
|
||||
if loginClient != "" {
|
||||
return o.createAuthRequestLoginClient(ctx, req, userID, loginClient)
|
||||
}
|
||||
|
||||
// if the instance requires the v2 login, use it no matter what the application configured
|
||||
if authz.GetFeatures(ctx).LoginV2.Required {
|
||||
return o.createAuthRequestLoginClient(ctx, req, userID, loginClient)
|
||||
@ -64,10 +69,7 @@ func (o *OPStorage) CreateAuthRequest(ctx context.Context, req *oidc.AuthRequest
|
||||
case domain.LoginVersionUnspecified:
|
||||
fallthrough
|
||||
default:
|
||||
// if undefined, use the v2 login if the header is sent, to retain the current behavior
|
||||
if loginClient != "" {
|
||||
return o.createAuthRequestLoginClient(ctx, req, userID, loginClient)
|
||||
}
|
||||
// since we already checked for a login header, we can fall back to the v1 login
|
||||
return o.createAuthRequest(ctx, req, userID)
|
||||
}
|
||||
}
|
||||
|
@ -307,7 +307,7 @@ func TestServer_VerifyClient(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
authRequestID, err := Instance.CreateOIDCAuthRequest(CTX, tt.client.authReqClientID, Instance.Users.Get(integration.UserTypeLogin).ID, redirectURI, oidc.ScopeOpenID)
|
||||
_, authRequestID, err := Instance.CreateOIDCAuthRequest(CTX, tt.client.authReqClientID, Instance.Users.Get(integration.UserTypeLogin).ID, redirectURI, oidc.ScopeOpenID)
|
||||
require.NoError(t, err)
|
||||
linkResp, err := Instance.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
|
||||
AuthRequestId: authRequestID,
|
||||
|
@ -441,13 +441,13 @@ func createImplicitClientNoLoginClientHeader(t testing.TB) string {
|
||||
}
|
||||
|
||||
func createAuthRequest(t testing.TB, instance *integration.Instance, clientID, redirectURI string, scope ...string) string {
|
||||
redURL, err := instance.CreateOIDCAuthRequest(CTX, clientID, instance.Users.Get(integration.UserTypeLogin).ID, redirectURI, scope...)
|
||||
_, redURL, err := instance.CreateOIDCAuthRequest(CTX, clientID, instance.Users.Get(integration.UserTypeLogin).ID, redirectURI, scope...)
|
||||
require.NoError(t, err)
|
||||
return redURL
|
||||
}
|
||||
|
||||
func createAuthRequestNoLoginClientHeader(t testing.TB, instance *integration.Instance, clientID, redirectURI string, scope ...string) string {
|
||||
redURL, err := instance.CreateOIDCAuthRequestWithoutLoginClientHeader(CTX, clientID, redirectURI, "", scope...)
|
||||
_, redURL, err := instance.CreateOIDCAuthRequestWithoutLoginClientHeader(CTX, clientID, redirectURI, "", scope...)
|
||||
require.NoError(t, err)
|
||||
return redURL
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ func (c *Commands) AddAuthRequest(ctx context.Context, authRequest *AuthRequest)
|
||||
return authRequestWriteModelToCurrentAuthRequest(writeModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) LinkSessionToAuthRequest(ctx context.Context, id, sessionID, sessionToken string, checkLoginClient bool) (*domain.ObjectDetails, *CurrentAuthRequest, error) {
|
||||
func (c *Commands) LinkSessionToAuthRequest(ctx context.Context, id, sessionID, sessionToken string, checkLoginClient bool, projectPermissionCheck domain.ProjectPermissionCheck) (*domain.ObjectDetails, *CurrentAuthRequest, error) {
|
||||
writeModel, err := c.getAuthRequestWriteModel(ctx, id)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@ -96,6 +96,7 @@ func (c *Commands) LinkSessionToAuthRequest(ctx context.Context, id, sessionID,
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
sessionWriteModel := NewSessionWriteModel(sessionID, authz.GetInstance(ctx).InstanceID())
|
||||
err = c.eventstore.FilterToQueryReducer(ctx, sessionWriteModel)
|
||||
if err != nil {
|
||||
@ -108,6 +109,12 @@ func (c *Commands) LinkSessionToAuthRequest(ctx context.Context, id, sessionID,
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if projectPermissionCheck != nil {
|
||||
if err := projectPermissionCheck(ctx, writeModel.ClientID, sessionWriteModel.UserID); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.pushAppendAndReduce(ctx, writeModel, authrequest.NewSessionLinkedEvent(
|
||||
ctx, &authrequest.NewAggregate(id, authz.GetInstance(ctx).InstanceID()).Aggregate,
|
||||
sessionID,
|
||||
|
@ -181,6 +181,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
|
||||
sessionID string
|
||||
sessionToken string
|
||||
checkLoginClient bool
|
||||
permissionCheck domain.ProjectPermissionCheck
|
||||
}
|
||||
type res struct {
|
||||
details *domain.ObjectDetails
|
||||
@ -712,6 +713,163 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"linked with permission, application permission check",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_id", "instanceID").Aggregate,
|
||||
"otherLoginClient",
|
||||
"clientID",
|
||||
"redirectURI",
|
||||
"state",
|
||||
"nonce",
|
||||
[]string{"openid"},
|
||||
[]string{"audience"},
|
||||
domain.OIDCResponseTypeCode,
|
||||
domain.OIDCResponseModeQuery,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
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(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "orgID", "loginClient"),
|
||||
id: "V2_id",
|
||||
sessionID: "sessionID",
|
||||
sessionToken: "token",
|
||||
checkLoginClient: true,
|
||||
permissionCheck: newMockProjectPermissionCheckAllowed(),
|
||||
},
|
||||
res{
|
||||
details: &domain.ObjectDetails{ResourceOwner: "instanceID"},
|
||||
authReq: &CurrentAuthRequest{
|
||||
AuthRequest: &AuthRequest{
|
||||
ID: "V2_id",
|
||||
LoginClient: "otherLoginClient",
|
||||
ClientID: "clientID",
|
||||
RedirectURI: "redirectURI",
|
||||
State: "state",
|
||||
Nonce: "nonce",
|
||||
Scope: []string{"openid"},
|
||||
Audience: []string{"audience"},
|
||||
ResponseType: domain.OIDCResponseTypeCode,
|
||||
ResponseMode: domain.OIDCResponseModeQuery,
|
||||
},
|
||||
SessionID: "sessionID",
|
||||
UserID: "userID",
|
||||
AuthMethods: []domain.UserAuthMethodType{domain.UserAuthMethodTypePassword},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"linked with permission, no application permission",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_id", "instanceID").Aggregate,
|
||||
"otherLoginClient",
|
||||
"clientID",
|
||||
"redirectURI",
|
||||
"state",
|
||||
"nonce",
|
||||
[]string{"openid"},
|
||||
[]string{"audience"},
|
||||
domain.OIDCResponseTypeCode,
|
||||
domain.OIDCResponseModeQuery,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
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(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "orgID", "loginClient"),
|
||||
id: "V2_id",
|
||||
sessionID: "sessionID",
|
||||
sessionToken: "token",
|
||||
checkLoginClient: true,
|
||||
permissionCheck: newMockProjectPermissionCheckOIDCNotAllowed(),
|
||||
},
|
||||
res{
|
||||
wantErr: zerrors.ThrowPermissionDenied(nil, "OIDC-foSyH49RvL", "Errors.PermissionDenied"),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@ -720,7 +878,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
|
||||
sessionTokenVerifier: tt.fields.tokenVerifier,
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
details, got, err := c.LinkSessionToAuthRequest(tt.args.ctx, tt.args.id, tt.args.sessionID, tt.args.sessionToken, tt.args.checkLoginClient)
|
||||
details, got, err := c.LinkSessionToAuthRequest(tt.args.ctx, tt.args.id, tt.args.sessionID, tt.args.sessionToken, tt.args.checkLoginClient, tt.args.permissionCheck)
|
||||
require.ErrorIs(t, err, tt.res.wantErr)
|
||||
assertObjectDetails(t, tt.res.details, details)
|
||||
if err == nil {
|
||||
|
@ -50,11 +50,6 @@ func projectAddedEvents(ctx context.Context, instanceID, orgID, id, owner string
|
||||
false,
|
||||
domain.PrivateLabelingSettingUnspecified,
|
||||
),
|
||||
project.NewProjectMemberAddedEvent(ctx,
|
||||
&project.NewAggregate(id, orgID).Aggregate,
|
||||
owner,
|
||||
domain.RoleProjectOwner,
|
||||
),
|
||||
instance.NewIAMProjectSetEvent(ctx,
|
||||
&instance.NewAggregate(instanceID).Aggregate,
|
||||
id,
|
||||
|
@ -232,6 +232,24 @@ func newMockPermissionCheckNotAllowed() domain.PermissionCheck {
|
||||
}
|
||||
}
|
||||
|
||||
func newMockProjectPermissionCheckAllowed() domain.ProjectPermissionCheck {
|
||||
return func(ctx context.Context, clientID, userID string) (err error) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func newMockProjectPermissionCheckOIDCNotAllowed() domain.ProjectPermissionCheck {
|
||||
return func(ctx context.Context, clientID, userID string) (err error) {
|
||||
return zerrors.ThrowPermissionDenied(nil, "OIDC-foSyH49RvL", "Errors.PermissionDenied")
|
||||
}
|
||||
}
|
||||
|
||||
func newMockProjectPermissionCheckSAMLNotAllowed() domain.ProjectPermissionCheck {
|
||||
return func(ctx context.Context, clientID, userID string) (err error) {
|
||||
return zerrors.ThrowPermissionDenied(nil, "SAML-foSyH49RvL", "Errors.PermissionDenied")
|
||||
}
|
||||
}
|
||||
|
||||
func newMockTokenVerifierValid() func(ctx context.Context, sessionToken, sessionID, tokenID string) (err error) {
|
||||
return func(ctx context.Context, sessionToken, sessionID, tokenID string) (err error) {
|
||||
return nil
|
||||
|
@ -96,16 +96,24 @@ func (c *orgSetupCommands) setupOrgAdmin(admin *OrgSetupAdmin, allowInitialMail
|
||||
c.validations = append(c.validations, c.commands.AddOrgMemberCommand(c.aggregate, admin.ID, orgAdminRoles(admin.Roles)...))
|
||||
return nil
|
||||
}
|
||||
userID, err := c.commands.idGenerator.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
var userID string
|
||||
if admin.Human != nil && admin.Human.ID != "" {
|
||||
userID = admin.Human.ID
|
||||
} else {
|
||||
var err error
|
||||
userID, err = c.commands.idGenerator.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if admin.Human != nil {
|
||||
admin.Human.ID = userID
|
||||
c.validations = append(c.validations, c.commands.AddHumanCommand(admin.Human, c.aggregate.ID, c.commands.userPasswordHasher, c.commands.userEncryption, allowInitialMail))
|
||||
} else if admin.Machine != nil {
|
||||
admin.Machine.Machine.AggregateID = userID
|
||||
if err = c.setupOrgAdminMachine(c.aggregate, admin.Machine); err != nil {
|
||||
if err := c.setupOrgAdminMachine(c.aggregate, admin.Machine); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -179,7 +187,7 @@ func (c *orgSetupCommands) push(ctx context.Context) (_ *CreatedOrg, err error)
|
||||
func (c *orgSetupCommands) createdAdmins() []*CreatedOrgAdmin {
|
||||
users := make([]*CreatedOrgAdmin, 0, len(c.admins))
|
||||
for _, admin := range c.admins {
|
||||
if admin.ID != "" {
|
||||
if admin.ID != "" && admin.Human == nil {
|
||||
continue
|
||||
}
|
||||
if admin.Human != nil {
|
||||
|
@ -26,13 +26,8 @@ func (c *Commands) AddProjectWithID(ctx context.Context, project *domain.Project
|
||||
if projectID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-nDXf5vXoUj", "Errors.IDMissing")
|
||||
}
|
||||
|
||||
existingProject, err := c.getProjectWriteModelByID(ctx, projectID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existingProject.State != domain.ProjectStateUnspecified {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-opamwu", "Errors.Project.AlreadyExisting")
|
||||
if !project.IsValid() {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-IOVCC", "Errors.Project.Invalid")
|
||||
}
|
||||
project, err = c.addProjectWithID(ctx, project, resourceOwner, projectID)
|
||||
if err != nil {
|
||||
@ -41,23 +36,22 @@ func (c *Commands) AddProjectWithID(ctx context.Context, project *domain.Project
|
||||
return project, nil
|
||||
}
|
||||
|
||||
func (c *Commands) AddProject(ctx context.Context, project *domain.Project, resourceOwner, ownerUserID string) (_ *domain.Project, err error) {
|
||||
func (c *Commands) AddProject(ctx context.Context, project *domain.Project, resourceOwner string) (_ *domain.Project, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
if !project.IsValid() {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-IOVCC", "Errors.Project.Invalid")
|
||||
}
|
||||
if resourceOwner == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-fmq7bqQX1s", "Errors.ResourceOwnerMissing")
|
||||
}
|
||||
if ownerUserID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-xe95Gl3Dro", "Errors.IDMissing")
|
||||
}
|
||||
|
||||
projectID, err := c.idGenerator.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
project, err = c.addProjectWithIDWithOwner(ctx, project, resourceOwner, ownerUserID, projectID)
|
||||
project, err = c.addProjectWithID(ctx, project, resourceOwner, projectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -66,13 +60,19 @@ func (c *Commands) AddProject(ctx context.Context, project *domain.Project, reso
|
||||
|
||||
func (c *Commands) addProjectWithID(ctx context.Context, projectAdd *domain.Project, resourceOwner, projectID string) (_ *domain.Project, err error) {
|
||||
projectAdd.AggregateID = projectID
|
||||
addedProject := NewProjectWriteModel(projectAdd.AggregateID, resourceOwner)
|
||||
projectAgg := ProjectAggregateFromWriteModel(&addedProject.WriteModel)
|
||||
projectWriteModel, err := c.getProjectWriteModelByID(ctx, projectAdd.AggregateID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isProjectStateExists(projectWriteModel.State) {
|
||||
return nil, zerrors.ThrowAlreadyExists(nil, "COMMAND-opamwu", "Errors.Project.AlreadyExisting")
|
||||
}
|
||||
|
||||
events := []eventstore.Command{
|
||||
project.NewProjectAddedEvent(
|
||||
ctx,
|
||||
projectAgg,
|
||||
//nolint: contextcheck
|
||||
ProjectAggregateFromWriteModel(&projectWriteModel.WriteModel),
|
||||
projectAdd.Name,
|
||||
projectAdd.ProjectRoleAssertion,
|
||||
projectAdd.ProjectRoleCheck,
|
||||
@ -88,47 +88,11 @@ func (c *Commands) addProjectWithID(ctx context.Context, projectAdd *domain.Proj
|
||||
return nil, err
|
||||
}
|
||||
postCommit(ctx)
|
||||
err = AppendAndReduce(addedProject, pushedEvents...)
|
||||
err = AppendAndReduce(projectWriteModel, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return projectWriteModelToProject(addedProject), nil
|
||||
}
|
||||
|
||||
func (c *Commands) addProjectWithIDWithOwner(ctx context.Context, projectAdd *domain.Project, resourceOwner, ownerUserID, projectID string) (_ *domain.Project, err error) {
|
||||
if !projectAdd.IsValid() {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-IOVCC", "Errors.Project.Invalid")
|
||||
}
|
||||
projectAdd.AggregateID = projectID
|
||||
addedProject := NewProjectWriteModel(projectAdd.AggregateID, resourceOwner)
|
||||
projectAgg := ProjectAggregateFromWriteModel(&addedProject.WriteModel)
|
||||
|
||||
projectRole := domain.RoleProjectOwner
|
||||
events := []eventstore.Command{
|
||||
project.NewProjectAddedEvent(
|
||||
ctx,
|
||||
projectAgg,
|
||||
projectAdd.Name,
|
||||
projectAdd.ProjectRoleAssertion,
|
||||
projectAdd.ProjectRoleCheck,
|
||||
projectAdd.HasProjectCheck,
|
||||
projectAdd.PrivateLabelingSetting),
|
||||
project.NewProjectMemberAddedEvent(ctx, projectAgg, ownerUserID, projectRole),
|
||||
}
|
||||
postCommit, err := c.projectCreatedMilestone(ctx, &events)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, events...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
postCommit(ctx)
|
||||
err = AppendAndReduce(addedProject, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return projectWriteModelToProject(addedProject), nil
|
||||
return projectWriteModelToProject(projectWriteModel), nil
|
||||
}
|
||||
|
||||
func AddProjectCommand(
|
||||
@ -159,9 +123,6 @@ func AddProjectCommand(
|
||||
hasProjectCheck,
|
||||
privateLabelingSetting,
|
||||
),
|
||||
project.NewProjectMemberAddedEvent(ctx, &a.Aggregate,
|
||||
owner,
|
||||
domain.RoleProjectOwner),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
@ -182,20 +143,6 @@ func projectWriteModel(ctx context.Context, filter preparation.FilterToQueryRedu
|
||||
return project, nil
|
||||
}
|
||||
|
||||
func (c *Commands) getProjectByID(ctx context.Context, projectID, resourceOwner string) (_ *domain.Project, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
projectWriteModel, err := c.getProjectWriteModelByID(ctx, projectID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if projectWriteModel.State == domain.ProjectStateUnspecified || projectWriteModel.State == domain.ProjectStateRemoved {
|
||||
return nil, zerrors.ThrowNotFound(nil, "PROJECT-Gd2hh", "Errors.Project.NotFound")
|
||||
}
|
||||
return projectWriteModelToProject(projectWriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) projectAggregateByID(ctx context.Context, projectID, resourceOwner string) (*eventstore.Aggregate, domain.ProjectState, error) {
|
||||
result, err := c.projectState(ctx, projectID, resourceOwner)
|
||||
if err != nil {
|
||||
@ -250,15 +197,11 @@ func (c *Commands) ChangeProject(ctx context.Context, projectChange *domain.Proj
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-4m9vS", "Errors.Project.Invalid")
|
||||
}
|
||||
|
||||
if !authz.GetFeatures(ctx).ShouldUseImprovedPerformance(feature.ImprovedPerformanceTypeProject) {
|
||||
return c.changeProjectOld(ctx, projectChange, resourceOwner)
|
||||
}
|
||||
|
||||
existingProject, err := c.getProjectWriteModelByID(ctx, projectChange.AggregateID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existingProject.State == domain.ProjectStateUnspecified || existingProject.State == domain.ProjectStateRemoved {
|
||||
if !isProjectStateExists(existingProject.State) {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-3M9sd", "Errors.Project.NotFound")
|
||||
}
|
||||
|
||||
@ -277,11 +220,7 @@ func (c *Commands) ChangeProject(ctx context.Context, projectChange *domain.Proj
|
||||
if !hasChanged {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-2M0fs", "Errors.NoChangesFound")
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, changedEvent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(existingProject, pushedEvents...)
|
||||
err = c.pushAppendAndReduce(ctx, existingProject, changedEvent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -302,7 +241,7 @@ func (c *Commands) DeactivateProject(ctx context.Context, projectID string, reso
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if state == domain.ProjectStateUnspecified || state == domain.ProjectStateRemoved {
|
||||
if !isProjectStateExists(state) {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-112M9", "Errors.Project.NotFound")
|
||||
}
|
||||
if state != domain.ProjectStateActive {
|
||||
@ -314,17 +253,6 @@ func (c *Commands) DeactivateProject(ctx context.Context, projectID string, reso
|
||||
return nil, err
|
||||
}
|
||||
|
||||
existingProject, err := c.getProjectWriteModelByID(ctx, projectID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existingProject.State == domain.ProjectStateUnspecified || existingProject.State == domain.ProjectStateRemoved {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-112M9", "Errors.Project.NotFound")
|
||||
}
|
||||
if existingProject.State != domain.ProjectStateActive {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-mki55", "Errors.Project.NotActive")
|
||||
}
|
||||
|
||||
return &domain.ObjectDetails{
|
||||
ResourceOwner: pushedEvents[0].Aggregate().ResourceOwner,
|
||||
Sequence: pushedEvents[0].Sequence(),
|
||||
@ -346,25 +274,13 @@ func (c *Commands) ReactivateProject(ctx context.Context, projectID string, reso
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if state == domain.ProjectStateUnspecified || state == domain.ProjectStateRemoved {
|
||||
if !isProjectStateExists(state) {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-3M9sd", "Errors.Project.NotFound")
|
||||
}
|
||||
|
||||
if state != domain.ProjectStateInactive {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-5M9bs", "Errors.Project.NotInactive")
|
||||
}
|
||||
|
||||
existingProject, err := c.getProjectWriteModelByID(ctx, projectID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existingProject.State == domain.ProjectStateUnspecified || existingProject.State == domain.ProjectStateRemoved {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-3M9sd", "Errors.Project.NotFound")
|
||||
}
|
||||
if existingProject.State != domain.ProjectStateInactive {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-5M9bs", "Errors.Project.NotInactive")
|
||||
}
|
||||
|
||||
pushedEvents, err := c.eventstore.Push(ctx, project.NewProjectReactivatedEvent(ctx, projectAgg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -382,15 +298,11 @@ func (c *Commands) RemoveProject(ctx context.Context, projectID, resourceOwner s
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-66hM9", "Errors.Project.ProjectIDMissing")
|
||||
}
|
||||
|
||||
if !authz.GetFeatures(ctx).ShouldUseImprovedPerformance(feature.ImprovedPerformanceTypeProject) {
|
||||
return c.removeProjectOld(ctx, projectID, resourceOwner)
|
||||
}
|
||||
|
||||
existingProject, err := c.getProjectWriteModelByID(ctx, projectID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existingProject.State == domain.ProjectStateUnspecified || existingProject.State == domain.ProjectStateRemoved {
|
||||
if !isProjectStateExists(existingProject.State) {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-3M9sd", "Errors.Project.NotFound")
|
||||
}
|
||||
|
||||
|
@ -78,11 +78,10 @@ func (c *Commands) AddAPIApplicationWithID(ctx context.Context, apiApp *domain.A
|
||||
if existingAPI.State != domain.AppStateUnspecified {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "PROJECT-mabu12", "Errors.Project.App.AlreadyExisting")
|
||||
}
|
||||
_, err = c.getProjectByID(ctx, apiApp.AggregateID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowPreconditionFailed(err, "PROJECT-9fnsa", "Errors.Project.NotFound")
|
||||
}
|
||||
|
||||
if err := c.checkProjectExists(ctx, apiApp.AggregateID, resourceOwner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.addAPIApplicationWithID(ctx, apiApp, resourceOwner, appID)
|
||||
}
|
||||
|
||||
@ -90,11 +89,10 @@ func (c *Commands) AddAPIApplication(ctx context.Context, apiApp *domain.APIApp,
|
||||
if apiApp == nil || apiApp.AggregateID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-5m9E", "Errors.Project.App.Invalid")
|
||||
}
|
||||
_, err = c.getProjectByID(ctx, apiApp.AggregateID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowPreconditionFailed(err, "PROJECT-9fnsf", "Errors.Project.NotFound")
|
||||
}
|
||||
|
||||
if err := c.checkProjectExists(ctx, apiApp.AggregateID, resourceOwner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !apiApp.IsValid() {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-Bff2g", "Errors.Project.App.Invalid")
|
||||
}
|
||||
|
@ -132,11 +132,9 @@ func (c *Commands) AddOIDCApplicationWithID(ctx context.Context, oidcApp *domain
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "PROJECT-lxowmp", "Errors.Project.App.AlreadyExisting")
|
||||
}
|
||||
|
||||
_, err = c.getProjectByID(ctx, oidcApp.AggregateID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowPreconditionFailed(err, "PROJECT-3m9s2", "Errors.Project.NotFound")
|
||||
if err := c.checkProjectExists(ctx, oidcApp.AggregateID, resourceOwner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.addOIDCApplicationWithID(ctx, oidcApp, resourceOwner, appID)
|
||||
}
|
||||
|
||||
@ -144,11 +142,9 @@ func (c *Commands) AddOIDCApplication(ctx context.Context, oidcApp *domain.OIDCA
|
||||
if oidcApp == nil || oidcApp.AggregateID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-34Fm0", "Errors.Project.App.Invalid")
|
||||
}
|
||||
_, err = c.getProjectByID(ctx, oidcApp.AggregateID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowPreconditionFailed(err, "PROJECT-3m9ss", "Errors.Project.NotFound")
|
||||
if err := c.checkProjectExists(ctx, oidcApp.AggregateID, resourceOwner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if oidcApp.AppName == "" || !oidcApp.IsValid() {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-1n8df", "Errors.Project.App.Invalid")
|
||||
}
|
||||
|
@ -16,11 +16,9 @@ func (c *Commands) AddSAMLApplication(ctx context.Context, application *domain.S
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-35Fn0", "Errors.Project.App.Invalid")
|
||||
}
|
||||
|
||||
_, err = c.getProjectByID(ctx, application.AggregateID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowPreconditionFailed(err, "PROJECT-3p9ss", "Errors.Project.NotFound")
|
||||
if err := c.checkProjectExists(ctx, application.AggregateID, resourceOwner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addedApplication := NewSAMLApplicationWriteModel(application.AggregateID, resourceOwner)
|
||||
projectAgg := ProjectAggregateFromWriteModel(&addedApplication.WriteModel)
|
||||
events, err := c.addSAMLApplication(ctx, projectAgg, application)
|
||||
|
@ -124,6 +124,19 @@ func (wm *ProjectWriteModel) NewChangedEvent(
|
||||
return changeEvent, true, nil
|
||||
}
|
||||
|
||||
func isProjectStateExists(state domain.ProjectState) bool {
|
||||
return !hasProjectState(state, domain.ProjectStateRemoved, domain.ProjectStateUnspecified)
|
||||
}
|
||||
|
||||
func ProjectAggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggregate {
|
||||
return eventstore.AggregateFromWriteModel(wm, project.AggregateType, project.AggregateVersion)
|
||||
}
|
||||
|
||||
func hasProjectState(check domain.ProjectState, states ...domain.ProjectState) bool {
|
||||
for _, state := range states {
|
||||
if check == state {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -3,10 +3,7 @@ package command
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/project"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
@ -20,58 +17,18 @@ func (c *Commands) checkProjectExistsOld(ctx context.Context, projectID, resourc
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if projectWriteModel.State == domain.ProjectStateUnspecified || projectWriteModel.State == domain.ProjectStateRemoved {
|
||||
if !isProjectStateExists(projectWriteModel.State) {
|
||||
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-EbFMN", "Errors.Project.NotFound")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) changeProjectOld(ctx context.Context, projectChange *domain.Project, resourceOwner string) (*domain.Project, error) {
|
||||
if !projectChange.IsValid() || projectChange.AggregateID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-4m9vS", "Errors.Project.Invalid")
|
||||
}
|
||||
|
||||
existingProject, err := c.getProjectWriteModelByID(ctx, projectChange.AggregateID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existingProject.State == domain.ProjectStateUnspecified || existingProject.State == domain.ProjectStateRemoved {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-3M9sd", "Errors.Project.NotFound")
|
||||
}
|
||||
|
||||
//nolint: contextcheck
|
||||
projectAgg := ProjectAggregateFromWriteModel(&existingProject.WriteModel)
|
||||
changedEvent, hasChanged, err := existingProject.NewChangedEvent(
|
||||
ctx,
|
||||
projectAgg,
|
||||
projectChange.Name,
|
||||
projectChange.ProjectRoleAssertion,
|
||||
projectChange.ProjectRoleCheck,
|
||||
projectChange.HasProjectCheck,
|
||||
projectChange.PrivateLabelingSetting)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !hasChanged {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-2M0fs", "Errors.NoChangesFound")
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, changedEvent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(existingProject, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return projectWriteModelToProject(existingProject), nil
|
||||
}
|
||||
|
||||
func (c *Commands) deactivateProjectOld(ctx context.Context, projectID string, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||
existingProject, err := c.getProjectWriteModelByID(ctx, projectID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existingProject.State == domain.ProjectStateUnspecified || existingProject.State == domain.ProjectStateRemoved {
|
||||
if !isProjectStateExists(existingProject.State) {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-112M9", "Errors.Project.NotFound")
|
||||
}
|
||||
if existingProject.State != domain.ProjectStateActive {
|
||||
@ -96,7 +53,7 @@ func (c *Commands) reactivateProjectOld(ctx context.Context, projectID string, r
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existingProject.State == domain.ProjectStateUnspecified || existingProject.State == domain.ProjectStateRemoved {
|
||||
if !isProjectStateExists(existingProject.State) {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-3M9sd", "Errors.Project.NotFound")
|
||||
}
|
||||
if existingProject.State != domain.ProjectStateInactive {
|
||||
@ -116,51 +73,6 @@ func (c *Commands) reactivateProjectOld(ctx context.Context, projectID string, r
|
||||
return writeModelToObjectDetails(&existingProject.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) removeProjectOld(ctx context.Context, projectID, resourceOwner string, cascadingUserGrantIDs ...string) (*domain.ObjectDetails, error) {
|
||||
existingProject, err := c.getProjectWriteModelByID(ctx, projectID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existingProject.State == domain.ProjectStateUnspecified || existingProject.State == domain.ProjectStateRemoved {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-3M9sd", "Errors.Project.NotFound")
|
||||
}
|
||||
|
||||
samlEntityIDsAgg, err := c.getSAMLEntityIdsWriteModelByProjectID(ctx, projectID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
uniqueConstraints := make([]*eventstore.UniqueConstraint, len(samlEntityIDsAgg.EntityIDs))
|
||||
for i, entityID := range samlEntityIDsAgg.EntityIDs {
|
||||
uniqueConstraints[i] = project.NewRemoveSAMLConfigEntityIDUniqueConstraint(entityID.EntityID)
|
||||
}
|
||||
|
||||
//nolint: contextcheck
|
||||
projectAgg := ProjectAggregateFromWriteModel(&existingProject.WriteModel)
|
||||
events := []eventstore.Command{
|
||||
project.NewProjectRemovedEvent(ctx, projectAgg, existingProject.Name, uniqueConstraints),
|
||||
}
|
||||
|
||||
for _, grantID := range cascadingUserGrantIDs {
|
||||
event, _, err := c.removeUserGrant(ctx, grantID, "", true)
|
||||
if err != nil {
|
||||
logging.WithFields("usergrantid", grantID).WithError(err).Warn("could not cascade remove user grant")
|
||||
continue
|
||||
}
|
||||
events = append(events, event)
|
||||
}
|
||||
|
||||
pushedEvents, err := c.eventstore.Push(ctx, events...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(existingProject, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&existingProject.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) checkProjectGrantPreConditionOld(ctx context.Context, projectGrant *domain.ProjectGrant, resourceOwner string) error {
|
||||
preConditions := NewProjectGrantPreConditionReadModel(projectGrant.AggregateID, projectGrant.GrantedOrgID, resourceOwner)
|
||||
err := c.eventstore.FilterToQueryReducer(ctx, preConditions)
|
||||
|
@ -25,7 +25,6 @@ func TestCommandSide_AddProject(t *testing.T) {
|
||||
ctx context.Context
|
||||
project *domain.Project
|
||||
resourceOwner string
|
||||
ownerID string
|
||||
}
|
||||
type res struct {
|
||||
want *domain.Project
|
||||
@ -54,7 +53,7 @@ func TestCommandSide_AddProject(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "org with project owner, resourceowner empty",
|
||||
name: "project, resourceowner empty",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
@ -70,40 +69,17 @@ func TestCommandSide_AddProject(t *testing.T) {
|
||||
PrivateLabelingSetting: domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy,
|
||||
},
|
||||
resourceOwner: "",
|
||||
ownerID: "user1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "org with project owner, ownerID empty",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
project: &domain.Project{
|
||||
Name: "project",
|
||||
ProjectRoleAssertion: true,
|
||||
ProjectRoleCheck: true,
|
||||
HasProjectCheck: true,
|
||||
PrivateLabelingSetting: domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy,
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
ownerID: "",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "org with project owner, error already exists",
|
||||
name: "project, error already exists",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
expectPushFailed(zerrors.ThrowAlreadyExists(nil, "ERROR", "internl"),
|
||||
project.NewProjectAddedEvent(
|
||||
context.Background(),
|
||||
@ -111,11 +87,36 @@ func TestCommandSide_AddProject(t *testing.T) {
|
||||
"project", true, true, true,
|
||||
domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy,
|
||||
),
|
||||
project.NewProjectMemberAddedEvent(
|
||||
context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"user1",
|
||||
[]string{domain.RoleProjectOwner}...,
|
||||
),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "project1"),
|
||||
},
|
||||
args: args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instanceID"),
|
||||
project: &domain.Project{
|
||||
Name: "project",
|
||||
ProjectRoleAssertion: true,
|
||||
ProjectRoleCheck: true,
|
||||
HasProjectCheck: true,
|
||||
PrivateLabelingSetting: domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy,
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorAlreadyExists,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "project, already exists",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"project", true, true, true,
|
||||
domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -131,17 +132,17 @@ func TestCommandSide_AddProject(t *testing.T) {
|
||||
PrivateLabelingSetting: domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy,
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
ownerID: "user1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorAlreadyExists,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "org with project owner, ok",
|
||||
name: "project, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
project.NewProjectAddedEvent(
|
||||
context.Background(),
|
||||
@ -149,12 +150,6 @@ func TestCommandSide_AddProject(t *testing.T) {
|
||||
"project", true, true, true,
|
||||
domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy,
|
||||
),
|
||||
project.NewProjectMemberAddedEvent(
|
||||
context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"user1",
|
||||
[]string{domain.RoleProjectOwner}...,
|
||||
),
|
||||
),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "project1"),
|
||||
@ -169,7 +164,6 @@ func TestCommandSide_AddProject(t *testing.T) {
|
||||
PrivateLabelingSetting: domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy,
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
ownerID: "user1",
|
||||
},
|
||||
res: res{
|
||||
want: &domain.Project{
|
||||
@ -193,7 +187,7 @@ func TestCommandSide_AddProject(t *testing.T) {
|
||||
idGenerator: tt.fields.idGenerator,
|
||||
}
|
||||
c.setMilestonesCompletedForTest("instanceID")
|
||||
got, err := c.AddProject(tt.args.ctx, tt.args.project, tt.args.resourceOwner, tt.args.ownerID)
|
||||
got, err := c.AddProject(tt.args.ctx, tt.args.project, tt.args.resourceOwner)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@ -1207,9 +1201,6 @@ func TestAddProject(t *testing.T) {
|
||||
false,
|
||||
domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy,
|
||||
),
|
||||
project.NewProjectMemberAddedEvent(ctx, &agg.Aggregate,
|
||||
"CAOS AG",
|
||||
domain.RoleProjectOwner),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -63,7 +63,7 @@ func (c *Commands) AddSAMLRequest(ctx context.Context, samlRequest *SAMLRequest)
|
||||
return samlRequestWriteModelToCurrentSAMLRequest(writeModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) LinkSessionToSAMLRequest(ctx context.Context, id, sessionID, sessionToken string, checkLoginClient bool) (*domain.ObjectDetails, *CurrentSAMLRequest, error) {
|
||||
func (c *Commands) LinkSessionToSAMLRequest(ctx context.Context, id, sessionID, sessionToken string, checkLoginClient bool, projectPermissionCheck domain.ProjectPermissionCheck) (*domain.ObjectDetails, *CurrentSAMLRequest, error) {
|
||||
writeModel, err := c.getSAMLRequestWriteModel(ctx, id)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@ -89,6 +89,12 @@ func (c *Commands) LinkSessionToSAMLRequest(ctx context.Context, id, sessionID,
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if projectPermissionCheck != nil {
|
||||
if err := projectPermissionCheck(ctx, writeModel.Issuer, sessionWriteModel.UserID); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.pushAppendAndReduce(ctx, writeModel, samlrequest.NewSessionLinkedEvent(
|
||||
ctx, &samlrequest.NewAggregate(id, authz.GetInstance(ctx).InstanceID()).Aggregate,
|
||||
sessionID,
|
||||
|
@ -141,6 +141,7 @@ func TestCommands_LinkSessionToSAMLRequest(t *testing.T) {
|
||||
sessionID string
|
||||
sessionToken string
|
||||
checkLoginClient bool
|
||||
checkPermission domain.ProjectPermissionCheck
|
||||
}
|
||||
type res struct {
|
||||
details *domain.ObjectDetails
|
||||
@ -524,6 +525,144 @@ func TestCommands_LinkSessionToSAMLRequest(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"linked with login client check, application permission check",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
samlrequest.NewAddedEvent(mockCtx, &samlrequest.NewAggregate("V2_id", "instanceID").Aggregate,
|
||||
"loginClient",
|
||||
"application",
|
||||
"acs",
|
||||
"relaystate",
|
||||
"request",
|
||||
"binding",
|
||||
"issuer",
|
||||
"destination",
|
||||
),
|
||||
),
|
||||
),
|
||||
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(
|
||||
samlrequest.NewSessionLinkedEvent(mockCtx, &samlrequest.NewAggregate("V2_id", "instanceID").Aggregate,
|
||||
"sessionID",
|
||||
"userID",
|
||||
testNow,
|
||||
[]domain.UserAuthMethodType{domain.UserAuthMethodTypePassword},
|
||||
),
|
||||
),
|
||||
),
|
||||
tokenVerifier: newMockTokenVerifierValid(),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "orgID", "loginClient"),
|
||||
id: "V2_id",
|
||||
sessionID: "sessionID",
|
||||
sessionToken: "token",
|
||||
checkLoginClient: true,
|
||||
checkPermission: newMockProjectPermissionCheckAllowed(),
|
||||
},
|
||||
res{
|
||||
details: &domain.ObjectDetails{ResourceOwner: "instanceID"},
|
||||
authReq: &CurrentSAMLRequest{
|
||||
SAMLRequest: &SAMLRequest{
|
||||
ID: "V2_id",
|
||||
LoginClient: "loginClient",
|
||||
ApplicationID: "application",
|
||||
ACSURL: "acs",
|
||||
RelayState: "relaystate",
|
||||
RequestID: "request",
|
||||
Binding: "binding",
|
||||
Issuer: "issuer",
|
||||
Destination: "destination",
|
||||
},
|
||||
SessionID: "sessionID",
|
||||
UserID: "userID",
|
||||
AuthMethods: []domain.UserAuthMethodType{domain.UserAuthMethodTypePassword},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"linked with login client check, no application permission",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
samlrequest.NewAddedEvent(mockCtx, &samlrequest.NewAggregate("V2_id", "instanceID").Aggregate,
|
||||
"loginClient",
|
||||
"application",
|
||||
"acs",
|
||||
"relaystate",
|
||||
"request",
|
||||
"binding",
|
||||
"issuer",
|
||||
"destination",
|
||||
),
|
||||
),
|
||||
),
|
||||
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: authz.NewMockContext("instanceID", "orgID", "loginClient"),
|
||||
id: "V2_id",
|
||||
sessionID: "sessionID",
|
||||
sessionToken: "token",
|
||||
checkLoginClient: true,
|
||||
checkPermission: newMockProjectPermissionCheckSAMLNotAllowed(),
|
||||
},
|
||||
res{
|
||||
wantErr: zerrors.ThrowPermissionDenied(nil, "SAML-foSyH49RvL", "Errors.PermissionDenied"),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@ -531,7 +670,7 @@ func TestCommands_LinkSessionToSAMLRequest(t *testing.T) {
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
sessionTokenVerifier: tt.fields.tokenVerifier,
|
||||
}
|
||||
details, got, err := c.LinkSessionToSAMLRequest(tt.args.ctx, tt.args.id, tt.args.sessionID, tt.args.sessionToken, tt.args.checkLoginClient)
|
||||
details, got, err := c.LinkSessionToSAMLRequest(tt.args.ctx, tt.args.id, tt.args.sessionID, tt.args.sessionToken, tt.args.checkLoginClient, tt.args.checkPermission)
|
||||
require.ErrorIs(t, err, tt.res.wantErr)
|
||||
assertObjectDetails(t, tt.res.details, details)
|
||||
if err == nil {
|
||||
|
@ -108,7 +108,7 @@ func (c *Commands) changeUserGrant(ctx context.Context, userGrant *domain.UserGr
|
||||
if cascade {
|
||||
return usergrant.NewUserGrantCascadeChangedEvent(ctx, userGrantAgg, userGrant.RoleKeys), existingUserGrant, nil
|
||||
}
|
||||
return usergrant.NewUserGrantChangedEvent(ctx, userGrantAgg, userGrant.RoleKeys), existingUserGrant, nil
|
||||
return usergrant.NewUserGrantChangedEvent(ctx, userGrantAgg, existingUserGrant.UserID, userGrant.RoleKeys), existingUserGrant, nil
|
||||
}
|
||||
|
||||
func (c *Commands) removeRoleFromUserGrant(ctx context.Context, userGrantID string, roleKeys []string, cascade bool) (_ eventstore.Command, err error) {
|
||||
@ -141,7 +141,7 @@ func (c *Commands) removeRoleFromUserGrant(ctx context.Context, userGrantID stri
|
||||
return usergrant.NewUserGrantCascadeChangedEvent(ctx, userGrantAgg, existingUserGrant.RoleKeys), nil
|
||||
}
|
||||
|
||||
return usergrant.NewUserGrantChangedEvent(ctx, userGrantAgg, existingUserGrant.RoleKeys), nil
|
||||
return usergrant.NewUserGrantChangedEvent(ctx, userGrantAgg, existingUserGrant.UserID, existingUserGrant.RoleKeys), nil
|
||||
}
|
||||
|
||||
func (c *Commands) DeactivateUserGrant(ctx context.Context, grantID, resourceOwner string) (objectDetails *domain.ObjectDetails, err error) {
|
||||
|
@ -1073,6 +1073,7 @@ func TestCommandSide_ChangeUserGrant(t *testing.T) {
|
||||
expectPush(
|
||||
usergrant.NewUserGrantChangedEvent(context.Background(),
|
||||
&usergrant.NewAggregate("usergrant1", "org1").Aggregate,
|
||||
"user1",
|
||||
[]string{"rolekey1", "rolekey2"},
|
||||
),
|
||||
),
|
||||
@ -1167,6 +1168,7 @@ func TestCommandSide_ChangeUserGrant(t *testing.T) {
|
||||
expectPush(
|
||||
usergrant.NewUserGrantChangedEvent(context.Background(),
|
||||
&usergrant.NewAggregate("usergrant1", "org1").Aggregate,
|
||||
"user1",
|
||||
[]string{"rolekey1", "rolekey2"},
|
||||
),
|
||||
),
|
||||
|
@ -97,6 +97,27 @@ func (c *Config) Connect(useAdmin bool) (*sql.DB, *pgxpool.Pool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if len(connConfig.BeforeAcquire) > 0 {
|
||||
config.BeforeAcquire = func(ctx context.Context, conn *pgx.Conn) bool {
|
||||
for _, f := range connConfig.BeforeAcquire {
|
||||
if err := f(ctx, conn); err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
if len(connConfig.AfterRelease) > 0 {
|
||||
config.AfterRelease = func(conn *pgx.Conn) bool {
|
||||
for _, f := range connConfig.AfterRelease {
|
||||
if err := f(conn); err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if connConfig.MaxOpenConns != 0 {
|
||||
config.MaxConns = int32(connConfig.MaxOpenConns)
|
||||
}
|
||||
|
@ -18,7 +18,9 @@ var (
|
||||
type ConnectionConfig struct {
|
||||
MaxOpenConns,
|
||||
MaxIdleConns uint32
|
||||
AfterConnect []func(ctx context.Context, c *pgx.Conn) error
|
||||
AfterConnect []func(ctx context.Context, c *pgx.Conn) error
|
||||
BeforeAcquire []func(ctx context.Context, c *pgx.Conn) error
|
||||
AfterRelease []func(c *pgx.Conn) error
|
||||
}
|
||||
|
||||
var afterConnectFuncs []func(ctx context.Context, c *pgx.Conn) error
|
||||
@ -27,6 +29,18 @@ func RegisterAfterConnect(f func(ctx context.Context, c *pgx.Conn) error) {
|
||||
afterConnectFuncs = append(afterConnectFuncs, f)
|
||||
}
|
||||
|
||||
var beforeAcquireFuncs []func(ctx context.Context, c *pgx.Conn) error
|
||||
|
||||
func RegisterBeforeAcquire(f func(ctx context.Context, c *pgx.Conn) error) {
|
||||
beforeAcquireFuncs = append(beforeAcquireFuncs, f)
|
||||
}
|
||||
|
||||
var afterReleaseFuncs []func(c *pgx.Conn) error
|
||||
|
||||
func RegisterAfterRelease(f func(c *pgx.Conn) error) {
|
||||
afterReleaseFuncs = append(afterReleaseFuncs, f)
|
||||
}
|
||||
|
||||
func RegisterDefaultPgTypeVariants[T any](m *pgtype.Map, name, arrayName string) {
|
||||
// T
|
||||
var value T
|
||||
@ -58,8 +72,10 @@ func RegisterDefaultPgTypeVariants[T any](m *pgtype.Map, name, arrayName string)
|
||||
// The pusherRatio and spoolerRatio must be between 0 and 1.
|
||||
func NewConnectionConfig(openConns, idleConns uint32) *ConnectionConfig {
|
||||
return &ConnectionConfig{
|
||||
MaxOpenConns: openConns,
|
||||
MaxIdleConns: idleConns,
|
||||
AfterConnect: afterConnectFuncs,
|
||||
MaxOpenConns: openConns,
|
||||
MaxIdleConns: idleConns,
|
||||
AfterConnect: afterConnectFuncs,
|
||||
BeforeAcquire: beforeAcquireFuncs,
|
||||
AfterRelease: afterReleaseFuncs,
|
||||
}
|
||||
}
|
||||
|
@ -81,13 +81,37 @@ func (c *Config) Connect(useAdmin bool) (*sql.DB, *pgxpool.Pool, error) {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
config.AfterConnect = func(ctx context.Context, conn *pgx.Conn) error {
|
||||
for _, f := range connConfig.AfterConnect {
|
||||
if err := f(ctx, conn); err != nil {
|
||||
return err
|
||||
if len(connConfig.AfterConnect) > 0 {
|
||||
config.AfterConnect = func(ctx context.Context, conn *pgx.Conn) error {
|
||||
for _, f := range connConfig.AfterConnect {
|
||||
if err := f(ctx, conn); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(connConfig.BeforeAcquire) > 0 {
|
||||
config.BeforeAcquire = func(ctx context.Context, conn *pgx.Conn) bool {
|
||||
for _, f := range connConfig.BeforeAcquire {
|
||||
if err := f(ctx, conn); err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if len(connConfig.AfterRelease) > 0 {
|
||||
config.AfterRelease = func(conn *pgx.Conn) bool {
|
||||
for _, f := range connConfig.AfterRelease {
|
||||
if err := f(conn); err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if connConfig.MaxOpenConns != 0 {
|
||||
|
@ -39,3 +39,7 @@ const (
|
||||
PermissionIDPRead = "iam.idp.read"
|
||||
PermissionOrgIDPRead = "org.idp.read"
|
||||
)
|
||||
|
||||
// ProjectPermissionCheck is used as a check for preconditions dependent on application, project, user resourceowner and usergrants.
|
||||
// Configurable on the project the application belongs to through the flags related to authentication.
|
||||
type ProjectPermissionCheck func(ctx context.Context, clientID, userID string) (err error)
|
||||
|
@ -678,6 +678,15 @@ func (i *Instance) CreatePasswordSession(t *testing.T, ctx context.Context, user
|
||||
createResp.GetDetails().GetChangeDate().AsTime(), createResp.GetDetails().GetChangeDate().AsTime()
|
||||
}
|
||||
|
||||
func (i *Instance) CreateProjectGrant(ctx context.Context, projectID, grantedOrgID string) *mgmt.AddProjectGrantResponse {
|
||||
resp, err := i.Client.Mgmt.AddProjectGrant(ctx, &mgmt.AddProjectGrantRequest{
|
||||
GrantedOrgId: grantedOrgID,
|
||||
ProjectId: projectID,
|
||||
})
|
||||
logging.OnError(err).Panic("create project grant")
|
||||
return resp
|
||||
}
|
||||
|
||||
func (i *Instance) CreateProjectUserGrant(t *testing.T, ctx context.Context, projectID, userID string) string {
|
||||
resp, err := i.Client.Mgmt.AddUserGrant(ctx, &mgmt.AddUserGrantRequest{
|
||||
UserId: userID,
|
||||
@ -687,6 +696,16 @@ func (i *Instance) CreateProjectUserGrant(t *testing.T, ctx context.Context, pro
|
||||
return resp.GetUserGrantId()
|
||||
}
|
||||
|
||||
func (i *Instance) CreateProjectGrantUserGrant(ctx context.Context, orgID, projectID, projectGrantID, userID string) string {
|
||||
resp, err := i.Client.Mgmt.AddUserGrant(SetOrgID(ctx, orgID), &mgmt.AddUserGrantRequest{
|
||||
UserId: userID,
|
||||
ProjectId: projectID,
|
||||
ProjectGrantId: projectGrantID,
|
||||
})
|
||||
logging.OnError(err).Panic("create project grant user grant")
|
||||
return resp.GetUserGrantId()
|
||||
}
|
||||
|
||||
func (i *Instance) CreateOrgMembership(t *testing.T, ctx context.Context, userID string) {
|
||||
_, err := i.Client.Mgmt.AddOrgMember(ctx, &mgmt.AddOrgMemberRequest{
|
||||
UserId: userID,
|
||||
|
@ -194,6 +194,14 @@ func (i *Instance) CreateProject(ctx context.Context) (*management.AddProjectRes
|
||||
})
|
||||
}
|
||||
|
||||
func (i *Instance) CreateProjectWithPermissionCheck(ctx context.Context, projectRoleCheck, hasProjectCheck bool) (*management.AddProjectResponse, error) {
|
||||
return i.Client.Mgmt.AddProject(ctx, &management.AddProjectRequest{
|
||||
Name: fmt.Sprintf("project-%d", time.Now().UnixNano()),
|
||||
HasProjectCheck: hasProjectCheck,
|
||||
ProjectRoleCheck: projectRoleCheck,
|
||||
})
|
||||
}
|
||||
|
||||
func (i *Instance) CreateAPIClientJWT(ctx context.Context, projectID string) (*management.AddAPIAppResponse, error) {
|
||||
return i.Client.Mgmt.AddAPIApp(ctx, &management.AddAPIAppRequest{
|
||||
ProjectId: projectID,
|
||||
@ -212,22 +220,22 @@ func (i *Instance) CreateAPIClientBasic(ctx context.Context, projectID string) (
|
||||
|
||||
const CodeVerifier = "codeVerifier"
|
||||
|
||||
func (i *Instance) CreateOIDCAuthRequest(ctx context.Context, clientID, loginClient, redirectURI string, scope ...string) (authRequestID string, err error) {
|
||||
func (i *Instance) CreateOIDCAuthRequest(ctx context.Context, clientID, loginClient, redirectURI string, scope ...string) (now time.Time, authRequestID string, err error) {
|
||||
return i.CreateOIDCAuthRequestWithDomain(ctx, i.Domain, clientID, loginClient, redirectURI, scope...)
|
||||
}
|
||||
|
||||
func (i *Instance) CreateOIDCAuthRequestWithoutLoginClientHeader(ctx context.Context, clientID, redirectURI, loginBaseURI string, scope ...string) (authRequestID string, err error) {
|
||||
func (i *Instance) CreateOIDCAuthRequestWithoutLoginClientHeader(ctx context.Context, clientID, redirectURI, loginBaseURI string, scope ...string) (now time.Time, authRequestID string, err error) {
|
||||
return i.createOIDCAuthRequestWithDomain(ctx, i.Domain, clientID, redirectURI, "", loginBaseURI, scope...)
|
||||
}
|
||||
|
||||
func (i *Instance) CreateOIDCAuthRequestWithDomain(ctx context.Context, domain, clientID, loginClient, redirectURI string, scope ...string) (authRequestID string, err error) {
|
||||
func (i *Instance) CreateOIDCAuthRequestWithDomain(ctx context.Context, domain, clientID, loginClient, redirectURI string, scope ...string) (now time.Time, authRequestID string, err error) {
|
||||
return i.createOIDCAuthRequestWithDomain(ctx, domain, clientID, redirectURI, loginClient, "", scope...)
|
||||
}
|
||||
|
||||
func (i *Instance) createOIDCAuthRequestWithDomain(ctx context.Context, domain, clientID, redirectURI, loginClient, loginBaseURI string, scope ...string) (authRequestID string, err error) {
|
||||
func (i *Instance) createOIDCAuthRequestWithDomain(ctx context.Context, domain, clientID, redirectURI, loginClient, loginBaseURI string, scope ...string) (now time.Time, authRequestID string, err error) {
|
||||
provider, err := i.CreateRelyingPartyForDomain(ctx, domain, clientID, redirectURI, loginClient, scope...)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("create relying party: %w", err)
|
||||
return now, "", fmt.Errorf("create relying party: %w", err)
|
||||
}
|
||||
codeChallenge := oidc.NewSHACodeChallenge(CodeVerifier)
|
||||
authURL := rp.AuthURL("state", provider, rp.WithCodeChallenge(codeChallenge))
|
||||
@ -238,21 +246,22 @@ func (i *Instance) createOIDCAuthRequestWithDomain(ctx context.Context, domain,
|
||||
}
|
||||
req, err := GetRequest(authURL, headers)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get request: %w", err)
|
||||
return now, "", fmt.Errorf("get request: %w", err)
|
||||
}
|
||||
|
||||
now = time.Now()
|
||||
loc, err := CheckRedirect(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("check redirect: %w", err)
|
||||
return now, "", fmt.Errorf("check redirect: %w", err)
|
||||
}
|
||||
|
||||
if loginBaseURI == "" {
|
||||
loginBaseURI = provider.Issuer() + i.Config.LoginURLV2
|
||||
}
|
||||
if !strings.HasPrefix(loc.String(), loginBaseURI) {
|
||||
return "", fmt.Errorf("login location has not prefix %s, but is %s", loginBaseURI, loc.String())
|
||||
return now, "", fmt.Errorf("login location has not prefix %s, but is %s", loginBaseURI, loc.String())
|
||||
}
|
||||
return strings.TrimPrefix(loc.String(), loginBaseURI), nil
|
||||
return now, strings.TrimPrefix(loc.String(), loginBaseURI), nil
|
||||
}
|
||||
|
||||
func (i *Instance) CreateOIDCAuthRequestImplicitWithoutLoginClientHeader(ctx context.Context, clientID, redirectURI string, scope ...string) (authRequestID string, err error) {
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/crewjam/saml"
|
||||
@ -135,32 +136,33 @@ func (i *Instance) CreateSAMLClient(ctx context.Context, projectID string, m *sa
|
||||
})
|
||||
}
|
||||
|
||||
func (i *Instance) CreateSAMLAuthRequest(m *samlsp.Middleware, loginClient string, acs saml.Endpoint, relayState string, responseBinding string) (authRequestID string, err error) {
|
||||
func (i *Instance) CreateSAMLAuthRequest(m *samlsp.Middleware, loginClient string, acs saml.Endpoint, relayState string, responseBinding string) (now time.Time, authRequestID string, err error) {
|
||||
authReq, err := m.ServiceProvider.MakeAuthenticationRequest(acs.Location, acs.Binding, responseBinding)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return now, "", err
|
||||
}
|
||||
|
||||
redirectURL, err := authReq.Redirect(relayState, &m.ServiceProvider)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return now, "", err
|
||||
}
|
||||
|
||||
req, err := GetRequest(redirectURL.String(), map[string]string{oidc_internal.LoginClientHeader: loginClient})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get request: %w", err)
|
||||
return now, "", fmt.Errorf("get request: %w", err)
|
||||
}
|
||||
|
||||
now = time.Now()
|
||||
loc, err := CheckRedirect(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("check redirect: %w", err)
|
||||
return now, "", fmt.Errorf("check redirect: %w", err)
|
||||
}
|
||||
|
||||
prefixWithHost := i.Issuer() + i.Config.LoginURLV2
|
||||
if !strings.HasPrefix(loc.String(), prefixWithHost) {
|
||||
return "", fmt.Errorf("login location has not prefix %s, but is %s", prefixWithHost, loc.String())
|
||||
return now, "", fmt.Errorf("login location has not prefix %s, but is %s", prefixWithHost, loc.String())
|
||||
}
|
||||
return strings.TrimPrefix(loc.String(), prefixWithHost), nil
|
||||
return now, strings.TrimPrefix(loc.String(), prefixWithHost), nil
|
||||
}
|
||||
|
||||
func (i *Instance) FailSAMLAuthRequest(ctx context.Context, id string, reason saml_pb.ErrorReason) *saml_pb.CreateResponseResponse {
|
||||
|
@ -79,7 +79,7 @@ func TestServer_TelemetryPushMilestones(t *testing.T) {
|
||||
func loginToClient(t *testing.T, instance *integration.Instance, clientID, redirectURI, sessionID, sessionToken string) {
|
||||
iamOwnerCtx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
authRequestID, err := instance.CreateOIDCAuthRequestWithDomain(iamOwnerCtx, instance.Domain, clientID, instance.Users.Get(integration.UserTypeIAMOwner).ID, redirectURI, "openid")
|
||||
_, authRequestID, err := instance.CreateOIDCAuthRequestWithDomain(iamOwnerCtx, instance.Domain, clientID, instance.Users.Get(integration.UserTypeIAMOwner).ID, redirectURI, "openid")
|
||||
require.NoError(t, err)
|
||||
callback, err := instance.Client.OIDCv2.CreateCallback(iamOwnerCtx, &oidc_v2.CreateCallbackRequest{
|
||||
AuthRequestId: authRequestID,
|
||||
|
@ -3,6 +3,7 @@ package query
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
_ "embed"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
@ -368,6 +369,77 @@ func (q *Queries) ProjectByClientID(ctx context.Context, appID string) (project
|
||||
return project, err
|
||||
}
|
||||
|
||||
//go:embed app_oidc_project_permission.sql
|
||||
var appOIDCProjectPermissionQuery string
|
||||
|
||||
func (q *Queries) CheckProjectPermissionByClientID(ctx context.Context, clientID, userID string) (_ *projectPermission, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
var p *projectPermission
|
||||
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
|
||||
p, err = scanProjectPermissionByClientID(row)
|
||||
return err
|
||||
}, appOIDCProjectPermissionQuery,
|
||||
authz.GetInstance(ctx).InstanceID(),
|
||||
clientID,
|
||||
domain.AppStateActive,
|
||||
domain.ProjectStateActive,
|
||||
userID,
|
||||
domain.UserStateActive,
|
||||
domain.ProjectGrantStateActive,
|
||||
domain.UserGrantStateActive,
|
||||
)
|
||||
return p, err
|
||||
}
|
||||
|
||||
//go:embed app_saml_project_permission.sql
|
||||
var appSAMLProjectPermissionQuery string
|
||||
|
||||
func (q *Queries) CheckProjectPermissionByEntityID(ctx context.Context, entityID, userID string) (_ *projectPermission, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
var p *projectPermission
|
||||
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
|
||||
p, err = scanProjectPermissionByClientID(row)
|
||||
return err
|
||||
}, appSAMLProjectPermissionQuery,
|
||||
authz.GetInstance(ctx).InstanceID(),
|
||||
entityID,
|
||||
domain.AppStateActive,
|
||||
domain.ProjectStateActive,
|
||||
userID,
|
||||
domain.UserStateActive,
|
||||
domain.ProjectGrantStateActive,
|
||||
domain.UserGrantStateActive,
|
||||
)
|
||||
return p, err
|
||||
}
|
||||
|
||||
type projectPermission struct {
|
||||
HasProjectChecked bool
|
||||
ProjectRoleChecked bool
|
||||
}
|
||||
|
||||
func scanProjectPermissionByClientID(row *sql.Row) (*projectPermission, error) {
|
||||
var hasProjectChecked, projectRoleChecked sql.NullBool
|
||||
err := row.Scan(
|
||||
&hasProjectChecked,
|
||||
&projectRoleChecked,
|
||||
)
|
||||
if err != nil || !hasProjectChecked.Valid || !projectRoleChecked.Valid {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, zerrors.ThrowNotFound(err, "QUERY-4tq8wCTCgf", "Errors.App.NotFound")
|
||||
}
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-NwH4lAqlZC", "Errors.Internal")
|
||||
}
|
||||
return &projectPermission{
|
||||
HasProjectChecked: hasProjectChecked.Bool,
|
||||
ProjectRoleChecked: projectRoleChecked.Bool,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (q *Queries) ProjectIDFromClientID(ctx context.Context, appID string) (id string, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
74
internal/query/app_oidc_project_permission.sql
Normal file
74
internal/query/app_oidc_project_permission.sql
Normal file
@ -0,0 +1,74 @@
|
||||
with application as (
|
||||
SELECT a.instance_id,
|
||||
a.resource_owner,
|
||||
a.project_id,
|
||||
a.id as app_id,
|
||||
p.project_role_check,
|
||||
p.has_project_check
|
||||
FROM projections.apps7 as a
|
||||
LEFT JOIN projections.apps7_oidc_configs as aoc
|
||||
ON aoc.app_id = a.id
|
||||
AND aoc.instance_id = a.instance_id
|
||||
INNER JOIN projections.projects4 as p
|
||||
ON p.instance_id = a.instance_id
|
||||
AND p.resource_owner = a.resource_owner
|
||||
AND p.id = a.project_id
|
||||
WHERE a.instance_id = $1
|
||||
AND aoc.client_id = $2
|
||||
AND a.state = $3
|
||||
AND p.state = $4
|
||||
), user_resourceowner as (
|
||||
/* resourceowner of the active user */
|
||||
SELECT u.instance_id,
|
||||
u.resource_owner,
|
||||
u.id as user_id
|
||||
FROM projections.users14 as u
|
||||
WHERE u.instance_id = $1
|
||||
AND u.id = $5
|
||||
AND u.state = $6
|
||||
), has_project_grant_check as (
|
||||
/* all projectgrants active, then filtered with the project and user resourceowner */
|
||||
SELECT pg.instance_id,
|
||||
pg.resource_owner,
|
||||
pg.project_id,
|
||||
pg.granted_org_id
|
||||
FROM projections.project_grants4 as pg
|
||||
WHERE pg.instance_id = $1
|
||||
AND pg.state = $7
|
||||
), project_role_check as (
|
||||
/* all usergrants active and associated with the user, then filtered with the project */
|
||||
SELECT ug.instance_id,
|
||||
ug.resource_owner,
|
||||
ug.project_id
|
||||
FROM projections.user_grants5 as ug
|
||||
WHERE ug.instance_id = $1
|
||||
AND ug.user_id = $5
|
||||
AND ug.state = $8
|
||||
)
|
||||
SELECT
|
||||
/* project existence does not need to be checked, or resourceowner of user and project are equal, or resourceowner of user has project granted*/
|
||||
bool_and(COALESCE(
|
||||
(NOT a.has_project_check OR
|
||||
a.resource_owner = uro.resource_owner OR
|
||||
uro.resource_owner = hpgc.granted_org_id)
|
||||
, FALSE)
|
||||
) as project_checked,
|
||||
/* authentication existence does not need to checked, or authentication for project is existing*/
|
||||
bool_and(COALESCE(
|
||||
(NOT a.project_role_check OR
|
||||
a.project_id = prc.project_id)
|
||||
, FALSE)
|
||||
) as role_checked
|
||||
FROM application as a
|
||||
LEFT JOIN user_resourceowner as uro
|
||||
ON uro.instance_id = a.instance_id
|
||||
LEFT JOIN has_project_grant_check as hpgc
|
||||
ON hpgc.instance_id = a.instance_id
|
||||
AND hpgc.project_id = a.project_id
|
||||
AND hpgc.granted_org_id = uro.resource_owner
|
||||
LEFT JOIN project_role_check as prc
|
||||
ON prc.instance_id = a.instance_id
|
||||
AND prc.project_id = a.project_id
|
||||
GROUP BY a.instance_id, a.resource_owner, a.project_id, a.app_id, uro.resource_owner, hpgc.granted_org_id,
|
||||
prc.project_id
|
||||
LIMIT 1;
|
74
internal/query/app_saml_project_permission.sql
Normal file
74
internal/query/app_saml_project_permission.sql
Normal file
@ -0,0 +1,74 @@
|
||||
with application as (
|
||||
SELECT a.instance_id,
|
||||
a.resource_owner,
|
||||
a.project_id,
|
||||
a.id as app_id,
|
||||
p.project_role_check,
|
||||
p.has_project_check
|
||||
FROM projections.apps7 as a
|
||||
LEFT JOIN projections.apps7_saml_configs as asaml
|
||||
ON asaml.app_id = a.id
|
||||
AND asaml.instance_id = a.instance_id
|
||||
INNER JOIN projections.projects4 as p
|
||||
ON p.instance_id = a.instance_id
|
||||
AND p.resource_owner = a.resource_owner
|
||||
AND p.id = a.project_id
|
||||
WHERE a.instance_id = $1
|
||||
AND asaml.entity_id = $2
|
||||
AND a.state = $3
|
||||
AND p.state = $4
|
||||
), user_resourceowner as (
|
||||
/* resourceowner of the active user */
|
||||
SELECT u.instance_id,
|
||||
u.resource_owner,
|
||||
u.id as user_id
|
||||
FROM projections.users14 as u
|
||||
WHERE u.instance_id = $1
|
||||
AND u.id = $5
|
||||
AND u.state = $6
|
||||
), has_project_grant_check as (
|
||||
/* all projectgrants active, then filtered with the project and user resourceowner */
|
||||
SELECT pg.instance_id,
|
||||
pg.resource_owner,
|
||||
pg.project_id,
|
||||
pg.granted_org_id
|
||||
FROM projections.project_grants4 as pg
|
||||
WHERE pg.instance_id = $1
|
||||
AND pg.state = $7
|
||||
), project_role_check as (
|
||||
/* all usergrants active and associated with the user, then filtered with the project */
|
||||
SELECT ug.instance_id,
|
||||
ug.resource_owner,
|
||||
ug.project_id
|
||||
FROM projections.user_grants5 as ug
|
||||
WHERE ug.instance_id = $1
|
||||
AND ug.user_id = $5
|
||||
AND ug.state = $8
|
||||
)
|
||||
SELECT
|
||||
/* project existence does not need to be checked, or resourceowner of user and project are equal, or resourceowner of user has project granted*/
|
||||
bool_and(COALESCE(
|
||||
(NOT a.has_project_check OR
|
||||
a.resource_owner = uro.resource_owner OR
|
||||
uro.resource_owner = hpgc.granted_org_id)
|
||||
, FALSE)
|
||||
) as project_checked,
|
||||
/* authentication existence does not need to checked, or authentication for project is existing*/
|
||||
bool_and(COALESCE(
|
||||
(NOT a.project_role_check OR
|
||||
a.project_id = prc.project_id)
|
||||
, FALSE)
|
||||
) as role_checked
|
||||
FROM application as a
|
||||
LEFT JOIN user_resourceowner as uro
|
||||
ON uro.instance_id = a.instance_id
|
||||
LEFT JOIN has_project_grant_check as hpgc
|
||||
ON hpgc.instance_id = a.instance_id
|
||||
AND hpgc.project_id = a.project_id
|
||||
AND hpgc.granted_org_id = uro.resource_owner
|
||||
LEFT JOIN project_role_check as prc
|
||||
ON prc.instance_id = a.instance_id
|
||||
AND prc.project_id = a.project_id
|
||||
GROUP BY a.instance_id, a.resource_owner, a.project_id, a.app_id, uro.resource_owner, hpgc.granted_org_id,
|
||||
prc.project_id
|
||||
LIMIT 1;
|
75
internal/queue/queue.go
Normal file
75
internal/queue/queue.go
Normal file
@ -0,0 +1,75 @@
|
||||
package queue
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/riverqueue/river/riverdriver"
|
||||
"github.com/riverqueue/river/riverdriver/riverpgxv5"
|
||||
"github.com/riverqueue/river/rivermigrate"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
"github.com/zitadel/zitadel/internal/database/dialect"
|
||||
)
|
||||
|
||||
const (
|
||||
schema = "queue"
|
||||
applicationName = "zitadel_queue"
|
||||
)
|
||||
|
||||
var conns = &sync.Map{}
|
||||
|
||||
type queueKey struct{}
|
||||
|
||||
func WithQueue(parent context.Context) context.Context {
|
||||
return context.WithValue(parent, queueKey{}, struct{}{})
|
||||
}
|
||||
|
||||
func init() {
|
||||
dialect.RegisterBeforeAcquire(func(ctx context.Context, c *pgx.Conn) error {
|
||||
if _, ok := ctx.Value(queueKey{}).(struct{}); !ok {
|
||||
return nil
|
||||
}
|
||||
_, err := c.Exec(ctx, "SET search_path TO "+schema+"; SET application_name TO "+applicationName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conns.Store(c, struct{}{})
|
||||
return nil
|
||||
})
|
||||
dialect.RegisterAfterRelease(func(c *pgx.Conn) error {
|
||||
_, ok := conns.LoadAndDelete(c)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
_, err := c.Exec(context.Background(), "SET search_path TO DEFAULT; SET application_name TO "+dialect.DefaultAppName)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// Queue abstracts the underlying queuing library
|
||||
// For more information see github.com/riverqueue/river
|
||||
// TODO(adlerhurst): maybe it makes more sense to split the effective queue from the migrator.
|
||||
type Queue struct {
|
||||
driver riverdriver.Driver[pgx.Tx]
|
||||
}
|
||||
|
||||
func New(client *database.DB) *Queue {
|
||||
return &Queue{driver: riverpgxv5.New(client.Pool)}
|
||||
}
|
||||
|
||||
func (q *Queue) ExecuteMigrations(ctx context.Context) error {
|
||||
_, err := q.driver.GetExecutor().Exec(ctx, "CREATE SCHEMA IF NOT EXISTS "+schema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
migrator, err := rivermigrate.New(q.driver, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx = WithQueue(ctx)
|
||||
_, err = migrator.Migrate(ctx, rivermigrate.DirectionUp, nil)
|
||||
return err
|
||||
}
|
@ -85,6 +85,7 @@ func UserGrantAddedEventMapper(event eventstore.Event) (eventstore.Event, error)
|
||||
|
||||
type UserGrantChangedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
UserID string `json:"userId"`
|
||||
RoleKeys []string `json:"roleKeys"`
|
||||
}
|
||||
|
||||
@ -99,6 +100,7 @@ func (e *UserGrantChangedEvent) UniqueConstraints() []*eventstore.UniqueConstrai
|
||||
func NewUserGrantChangedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
userID string,
|
||||
roleKeys []string) *UserGrantChangedEvent {
|
||||
return &UserGrantChangedEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
@ -106,6 +108,7 @@ func NewUserGrantChangedEvent(
|
||||
aggregate,
|
||||
UserGrantChangedType,
|
||||
),
|
||||
UserID: userID,
|
||||
RoleKeys: roleKeys,
|
||||
}
|
||||
}
|
||||
@ -165,17 +168,17 @@ func UserGrantCascadeChangedEventMapper(event eventstore.Event) (eventstore.Even
|
||||
|
||||
type UserGrantRemovedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
userID string `json:"-"`
|
||||
projectID string `json:"-"`
|
||||
projectGrantID string `json:"-"`
|
||||
UserID string `json:"userId,omitempty"`
|
||||
ProjectID string `json:"projectId,omitempty"`
|
||||
ProjectGrantID string `json:"grantId,omitempty"`
|
||||
}
|
||||
|
||||
func (e *UserGrantRemovedEvent) Payload() interface{} {
|
||||
return nil
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *UserGrantRemovedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||
return []*eventstore.UniqueConstraint{NewRemoveUserGrantUniqueConstraint(e.Aggregate().ResourceOwner, e.userID, e.projectID, e.projectGrantID)}
|
||||
return []*eventstore.UniqueConstraint{NewRemoveUserGrantUniqueConstraint(e.Aggregate().ResourceOwner, e.UserID, e.ProjectID, e.ProjectGrantID)}
|
||||
}
|
||||
|
||||
func NewUserGrantRemovedEvent(
|
||||
@ -191,9 +194,9 @@ func NewUserGrantRemovedEvent(
|
||||
aggregate,
|
||||
UserGrantRemovedType,
|
||||
),
|
||||
userID: userID,
|
||||
projectID: projectID,
|
||||
projectGrantID: projectGrantID,
|
||||
UserID: userID,
|
||||
ProjectID: projectID,
|
||||
ProjectGrantID: projectGrantID,
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user