Merge branch 'main' into remove-stable

This commit is contained in:
Livio Spring 2024-12-02 09:32:08 +01:00 committed by GitHub
commit 64ed2c25e2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
209 changed files with 9311 additions and 3078 deletions

View File

@ -77,7 +77,7 @@ jobs:
go_version: "1.22"
node_version: "18"
buf_version: "latest"
go_lint_version: "v1.55.2"
go_lint_version: "v1.62.2"
core_cache_key: ${{ needs.core.outputs.cache_key }}
core_cache_path: ${{ needs.core.outputs.cache_path }}

16
ADOPTERS.md Normal file
View File

@ -0,0 +1,16 @@
## Adopters
Sharing experiences and learning from other users is essential. We are frequently asked who is using a particular feature of Zitadel so people can get in touch with other users to share experiences and best practices. People also often want to know if a specific product or platform has integrated Zitadel. While the Zitadel Discord Community allows users to get in touch, it can be challenging to find this information quickly.
The following is a directory of adopters to help identify users of individual features. The users themselves directly maintain the list.
### Adding yourself as a user
If you are using Zitadel, please consider adding yourself as a user with a quick description of your use case by opening a pull request to this file and adding a section describing your usage of Zitadel.
| Organization/Individual | Contact Information | Description of Usage |
| ----------------------- | -------------------------------------------------------------------- | ----------------------------------------------- |
| Zitadel | [@fforootd](https://github.com/fforootd) (and many more) | Zitadel Cloud makes heavy use of of Zitadel ;-) |
| Rawkode Academy | [@RawkodeAcademy](https://github.com/RawkodeAcademy) | Rawkode Academy Platform & Zulip use Zitadel for all user and M2M authentication |
| Organization Name | contact@example.com | Description of how they use Zitadel |
| Individual Name | contact@example.com | Description of how they use Zitadel |

View File

@ -3,6 +3,35 @@
Dear community!
We're excited to announce bi-weekly office hours.
## #6 Q&A
Hey folks!
Were inviting you to our next open office hours session! C: From leveraging ZITADEL actions to exploring your use cases, join our hosts Silvan & Stefan on Wednesday, November 20, 2024 at 11:00 AM (EST) as they answer your questions about ZITADEL!
🦒 **What to expect**
An open Q&A session - Share your questions and support others with their inquiries.
A space to share your thoughts / feedback on the ZITADEL platform
🗒️ **Details**
Target audience: All ZITADEL platform users & community members
Topic: Q&A Session
Date & time: Wednesday, November 20, 2024 at 11:00 AM (EST)
Duration: ~1 hour
Platform: ZITADELs Discord stage channel
Register for this event here ➡️ https://discord.gg/bnuAe2RX?event=1307010383713927230
🗓️ **Add this to your calendar** ➡️ [Google Calendar](https://calendar.google.com/calendar/u/0/r/eventedit?dates=20241120T110000/20241120T110000&details=We%E2%80%99re+inviting+you+to+our+next+open+office+hours+session!+C:+From+leveraging+ZITADEL+actions+to+exploring+your+use+cases,+join+our+hosts+Silvan+%26+Stefan+as+they+answer+your+questions+about+ZITADEL!+%0A%0A**What+to+expect**%0A%0A-+An+open+Q%26A+session+-+Share+your+questions+and+support+others+with+their+inquiries.+%0A-+A+space+to+share+your+thoughts+/+feedback+on+the+ZITADEL+platform+++%0A%0A**Details**+%0A%0A**Target+audience:**+All+ZITADEL+platform+users+%26+community+members%0A**Topic**:+Q%26A+Session+%0A**Date+%26+time**:+Wednesday,+November+20,+2024+11:00+AM%0A**Duration**:+~1+hour+%0A**Platform**:+ZITADEL%E2%80%99s+Discord+stage+channel&location=Discord:+ZITADEL+server,+office+hours&text=Open+Office+Hours)
If you have any questions prior to the live session, be sure to share them in the office hours stage chat
Looking forward to seeing you there! Share this with other ZITADEL users & people who might be interested in ZITADEL! Its appreciated 🫶
## #5 Q&A
Dear community,

View File

@ -87,6 +87,10 @@ Available data regions are:
ZITADEL Cloud comes with a free tier, providing you with all the same features as the open-source version.
Learn more about the [pay-as-you-go pricing](https://zitadel.com/pricing).
## Adopters
We are grateful to the organizations and individuals who are using ZITADEL. If you are using ZITADEL, please consider adding your name to our [Adopters list](./ADOPTERS.md) by submitting a pull request.
### Example applications
Clone one of our [example applications](https://zitadel.com/docs/sdk-examples/introduction) or deploy them directly to Vercel.

File diff suppressed because one or more lines are too long

View File

@ -17,6 +17,7 @@ var (
"smsKey",
"smtpKey",
"userKey",
"targetKey",
"csrfCookieKey",
"userAgentCookieKey",
}
@ -31,6 +32,7 @@ type EncryptionKeyConfig struct {
SMS *crypto.KeyConfig
SMTP *crypto.KeyConfig
User *crypto.KeyConfig
Target *crypto.KeyConfig
CSRFCookieKeyID string
UserAgentCookieKeyID string
}
@ -44,6 +46,7 @@ type EncryptionKeys struct {
SMS crypto.EncryptionAlgorithm
SMTP crypto.EncryptionAlgorithm
User crypto.EncryptionAlgorithm
Target crypto.EncryptionAlgorithm
CSRFCookieKey []byte
UserAgentCookieKey []byte
OIDCKey []byte
@ -91,6 +94,10 @@ func EnsureEncryptionKeys(ctx context.Context, keyConfig *EncryptionKeyConfig, k
if err != nil {
return nil, err
}
keys.Target, err = crypto.NewAESCrypto(keyConfig.Target, keyStorage)
if err != nil {
return nil, err
}
key, err = crypto.LoadKey(keyConfig.CSRFCookieKeyID, keyStorage)
if err != nil {
return nil, err

View File

@ -69,6 +69,7 @@ func projectionsCmd() *cobra.Command {
type ProjectionsConfig struct {
Destination database.Config
Projections projection.Config
Notifications handlers.WorkerConfig
EncryptionKeys *encryption.EncryptionKeyConfig
SystemAPIUsers map[string]*internal_authz.SystemAPIUser
Eventstore *eventstore.Config
@ -144,6 +145,7 @@ func projections(
keys.OTP,
keys.OIDC,
keys.SAML,
keys.Target,
config.InternalAuthZ.RolePermissionMappings,
sessionTokenVerifier,
func(q *query.Queries) domain.PermissionCheck {
@ -182,6 +184,7 @@ func projections(
keys.DomainVerification,
keys.OIDC,
keys.SAML,
keys.Target,
&http.Client{},
func(ctx context.Context, permission, orgID, resourceID string) (err error) {
return internal_authz.CheckPermission(ctx, authZRepo, config.InternalAuthZ.RolePermissionMappings, permission, orgID, resourceID)
@ -205,6 +208,7 @@ func projections(
config.Projections.Customizations["notificationsquotas"],
config.Projections.Customizations["backchannel"],
config.Projections.Customizations["telemetry"],
config.Notifications,
*config.Telemetry,
config.ExternalDomain,
config.ExternalPort,
@ -219,6 +223,7 @@ func projections(
keys.SMS,
keys.OIDC,
config.OIDC.DefaultBackChannelLogoutLifetime,
client,
)
config.Auth.Spooler.Client = client

View File

@ -5,13 +5,16 @@ import (
"database/sql"
_ "embed"
"fmt"
"slices"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/zitadel/logging"
cryptoDatabase "github.com/zitadel/zitadel/internal/crypto/database"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/database/dialect"
"github.com/zitadel/zitadel/internal/query/projection"
)
func verifyCmd() *cobra.Command {
@ -98,12 +101,22 @@ func getViews(ctx context.Context, dest *database.DB, schema string) (tables []s
}
func countEntries(ctx context.Context, client *database.DB, table string) (count int) {
instanceClause := instanceClause()
noInstanceIDColumn := []string{
projection.InstanceProjectionTable,
projection.SystemFeatureTable,
cryptoDatabase.EncryptionKeysTable,
}
if slices.Contains(noInstanceIDColumn, table) {
instanceClause = ""
}
err := client.QueryRowContext(
ctx,
func(r *sql.Row) error {
return r.Scan(&count)
},
fmt.Sprintf("SELECT COUNT(*) FROM %s %s", table, instanceClause()),
fmt.Sprintf("SELECT COUNT(*) FROM %s %s", table, instanceClause),
)
logging.WithFields("table", table, "db", client.DatabaseName()).OnError(err).Error("unable to count")

View File

@ -86,6 +86,7 @@ func (mig *FirstInstance) Execute(ctx context.Context, _ eventstore.Event) error
nil,
nil,
nil,
nil,
0,
0,
0,

View File

@ -23,7 +23,7 @@ var (
getProjectedMilestones string
)
type FillV2Milestones struct {
type FillV3Milestones struct {
dbClient *database.DB
eventstore *eventstore.Eventstore
}
@ -34,7 +34,7 @@ type instanceMilestone struct {
Pushed *time.Time
}
func (mig *FillV2Milestones) Execute(ctx context.Context, _ eventstore.Event) error {
func (mig *FillV3Milestones) Execute(ctx context.Context, _ eventstore.Event) error {
im, err := mig.getProjectedMilestones(ctx)
if err != nil {
return err
@ -42,7 +42,7 @@ func (mig *FillV2Milestones) Execute(ctx context.Context, _ eventstore.Event) er
return mig.pushEventsByInstance(ctx, im)
}
func (mig *FillV2Milestones) getProjectedMilestones(ctx context.Context) (map[string][]instanceMilestone, error) {
func (mig *FillV3Milestones) getProjectedMilestones(ctx context.Context) (map[string][]instanceMilestone, error) {
type row struct {
InstanceID string
Type milestone.Type
@ -73,7 +73,7 @@ func (mig *FillV2Milestones) getProjectedMilestones(ctx context.Context) (map[st
// pushEventsByInstance creates the v2 milestone events by instance.
// This prevents we will try to push 6*N(instance) events in one push.
func (mig *FillV2Milestones) pushEventsByInstance(ctx context.Context, milestoneMap map[string][]instanceMilestone) error {
func (mig *FillV3Milestones) pushEventsByInstance(ctx context.Context, milestoneMap map[string][]instanceMilestone) error {
// keep a deterministic order by instance ID.
order := make([]string, 0, len(milestoneMap))
for k := range milestoneMap {
@ -81,8 +81,8 @@ func (mig *FillV2Milestones) pushEventsByInstance(ctx context.Context, milestone
}
slices.Sort(order)
for _, instanceID := range order {
logging.WithFields("instance_id", instanceID, "migration", mig.String()).Info("filter existing milestone events")
for i, instanceID := range order {
logging.WithFields("instance_id", instanceID, "migration", mig.String(), "progress", fmt.Sprintf("%d/%d", i+1, len(order))).Info("filter existing milestone events")
// because each Push runs in a separate TX, we need to make sure that events
// from a partially executed migration are pushed again.
@ -113,6 +113,6 @@ func (mig *FillV2Milestones) pushEventsByInstance(ctx context.Context, milestone
return nil
}
func (mig *FillV2Milestones) String() string {
return "36_fill_v2_milestones"
func (mig *FillV3Milestones) String() string {
return "36_fill_v3_milestones"
}

27
cmd/setup/39.go Normal file
View File

@ -0,0 +1,27 @@
package setup
import (
"context"
_ "embed"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 39.sql
deleteStaleOrgFields string
)
type DeleteStaleOrgFields struct {
dbClient *database.DB
}
func (mig *DeleteStaleOrgFields) Execute(ctx context.Context, _ eventstore.Event) error {
_, err := mig.dbClient.ExecContext(ctx, deleteStaleOrgFields)
return err
}
func (mig *DeleteStaleOrgFields) String() string {
return "39_delete_stale_org_fields"
}

6
cmd/setup/39.sql Normal file
View File

@ -0,0 +1,6 @@
DELETE FROM eventstore.fields
WHERE aggregate_type = 'org'
AND aggregate_id NOT IN (
SELECT id
FROM projections.orgs1
);

View File

@ -42,6 +42,7 @@ type Config struct {
DefaultInstance command.InstanceSetup
Machine *id.Config
Projections projection.Config
Notifications handlers.WorkerConfig
Eventstore *eventstore.Config
InitProjections InitProjections
@ -122,9 +123,10 @@ type Steps struct {
s33SMSConfigs3TwilioAddVerifyServiceSid *SMSConfigs3TwilioAddVerifyServiceSid
s34AddCacheSchema *AddCacheSchema
s35AddPositionToIndexEsWm *AddPositionToIndexEsWm
s36FillV2Milestones *FillV2Milestones
s36FillV2Milestones *FillV3Milestones
s37Apps7OIDConfigsBackChannelLogoutURI *Apps7OIDConfigsBackChannelLogoutURI
s38BackChannelLogoutNotificationStart *BackChannelLogoutNotificationStart
s39DeleteStaleOrgFields *DeleteStaleOrgFields
}
func MustNewSteps(v *viper.Viper) *Steps {

View File

@ -53,6 +53,7 @@ func (mig *externalConfigChange) Execute(ctx context.Context, _ eventstore.Event
nil,
nil,
nil,
nil,
0,
0,
0,

View File

@ -166,9 +166,10 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string)
steps.s33SMSConfigs3TwilioAddVerifyServiceSid = &SMSConfigs3TwilioAddVerifyServiceSid{dbClient: esPusherDBClient}
steps.s34AddCacheSchema = &AddCacheSchema{dbClient: queryDBClient}
steps.s35AddPositionToIndexEsWm = &AddPositionToIndexEsWm{dbClient: esPusherDBClient}
steps.s36FillV2Milestones = &FillV2Milestones{dbClient: queryDBClient, eventstore: eventstoreClient}
steps.s36FillV2Milestones = &FillV3Milestones{dbClient: queryDBClient, eventstore: eventstoreClient}
steps.s37Apps7OIDConfigsBackChannelLogoutURI = &Apps7OIDConfigsBackChannelLogoutURI{dbClient: esPusherDBClient}
steps.s38BackChannelLogoutNotificationStart = &BackChannelLogoutNotificationStart{dbClient: esPusherDBClient, esClient: eventstoreClient}
steps.s39DeleteStaleOrgFields = &DeleteStaleOrgFields{dbClient: esPusherDBClient}
err = projection.Create(ctx, projectionDBClient, eventstoreClient, config.Projections, nil, nil, nil)
logging.OnError(err).Fatal("unable to start projections")
@ -232,6 +233,7 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string)
steps.s32AddAuthSessionID,
steps.s33SMSConfigs3TwilioAddVerifyServiceSid,
steps.s37Apps7OIDConfigsBackChannelLogoutURI,
steps.s39DeleteStaleOrgFields,
} {
mustExecuteMigration(ctx, eventstoreClient, step, "migration failed")
}
@ -364,6 +366,7 @@ func initProjections(
keys.OTP,
keys.OIDC,
keys.SAML,
keys.Target,
config.InternalAuthZ.RolePermissionMappings,
sessionTokenVerifier,
func(q *query.Queries) domain.PermissionCheck {
@ -420,6 +423,7 @@ func initProjections(
keys.DomainVerification,
keys.OIDC,
keys.SAML,
keys.Target,
&http.Client{},
permissionCheck,
sessionTokenVerifier,
@ -435,6 +439,7 @@ func initProjections(
config.Projections.Customizations["notificationsquotas"],
config.Projections.Customizations["backchannel"],
config.Projections.Customizations["telemetry"],
config.Notifications,
*config.Telemetry,
config.ExternalDomain,
config.ExternalPort,
@ -449,6 +454,7 @@ func initProjections(
keys.SMS,
keys.OIDC,
config.OIDC.DefaultBackChannelLogoutLifetime,
queryDBClient,
)
for _, p := range notify_handler.Projections() {
err := migration.Migrate(ctx, eventstoreClient, p)

View File

@ -54,6 +54,7 @@ type Config struct {
Metrics metrics.Config
Profiler profiler.Config
Projections projection.Config
Notifications handlers.WorkerConfig
Auth auth_es.Config
Admin admin_es.Config
UserAgentCookie *middleware.UserAgentCookieConfig

View File

@ -196,6 +196,7 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server
keys.OTP,
keys.OIDC,
keys.SAML,
keys.Target,
config.InternalAuthZ.RolePermissionMappings,
sessionTokenVerifier,
func(q *query.Queries) domain.PermissionCheck {
@ -245,6 +246,7 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server
keys.DomainVerification,
keys.OIDC,
keys.SAML,
keys.Target,
&http.Client{},
permissionCheck,
sessionTokenVerifier,
@ -277,6 +279,7 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server
config.Projections.Customizations["notificationsquotas"],
config.Projections.Customizations["backchannel"],
config.Projections.Customizations["telemetry"],
config.Notifications,
*config.Telemetry,
config.ExternalDomain,
config.ExternalPort,
@ -291,6 +294,7 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server
keys.SMS,
keys.OIDC,
config.OIDC.DefaultBackChannelLogoutLifetime,
queryDBClient,
)
notification.Start(ctx)

View File

@ -8,12 +8,10 @@
</p>
</div>
<span class="fill-space"></span>
<span class="pos cnsl-secondary-text" *ngIf="!hidePagination"
>{{ pageIndex * pageSize }} - {{ pageIndex * pageSize + pageSize }}
</span>
<span class="pos cnsl-secondary-text" *ngIf="!hidePagination">{{ startIndex }} - {{ endIndex }} </span>
<div class="row" *ngIf="!hidePagination">
<cnsl-form-field class="size">
<mat-select class="paginator-select" [(ngModel)]="pageSize" (selectionChange)="emitChange()">
<mat-select class="paginator-select" [value]="pageSize" (selectionChange)="updatePageSize($event.value)">
<mat-option *ngFor="let sizeOption of pageSizeOptions" [value]="sizeOption">
{{ sizeOption }}
</mat-option>

View File

@ -50,6 +50,15 @@ export class PaginatorComponent {
return temp <= this.length / this.pageSize;
}
get startIndex(): number {
return this.pageIndex * this.pageSize;
}
get endIndex(): number {
const max = this.startIndex + this.pageSize;
return this.length < max ? this.length : max;
}
public emitChange(): void {
this.page.emit({
length: this.length,
@ -58,4 +67,10 @@ export class PaginatorComponent {
pageSizeOptions: this.pageSizeOptions,
});
}
public updatePageSize(newSize: number): void {
this.pageSize = newSize;
this.pageIndex = 0;
this.emitChange();
}
}

View File

@ -31,9 +31,9 @@ export class ProjectRoleDetailDialogComponent {
}
submitForm(): void {
if (this.formGroup.valid && this.key?.value && this.group?.value && this.displayName?.value) {
if (this.formGroup.valid && this.key?.value && this.displayName?.value) {
this.mgmtService
.updateProjectRole(this.projectId, this.key.value, this.displayName.value, this.group.value)
.updateProjectRole(this.projectId, this.key.value, this.displayName.value, this.group?.value)
.then(() => {
this.toast.showInfo('PROJECT.TOAST.ROLECHANGED', true);
this.dialogRef.close(true);

View File

@ -35,8 +35,8 @@
[disabled]="disabled"
color="primary"
(change)="$event ? masterToggle() : null"
[checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()"
[checked]="isAnySelected() && isAllSelected()"
[indeterminate]="isAnySelected() && !isAllSelected()"
>
</mat-checkbox>
</div>
@ -76,7 +76,7 @@
class="role state"
[ngClass]="{ 'no-selection': !selectionAllowed }"
*ngIf="role.group"
(click)="selectionAllowed ? selectAllOfGroup(role.group) : openDetailDialog(role)"
(click)="selectionAllowed ? groupMasterToggle(role.group) : openDetailDialog(role)"
[matTooltip]="selectionAllowed ? ('PROJECT.ROLE.SELECTGROUPTOOLTIP' | translate: role) : null"
>{{ role.group }}</span
>
@ -135,7 +135,7 @@
#paginator
[timestamp]="dataSource.viewTimestamp"
[length]="dataSource.totalResult"
[pageSize]="50"
[pageSize]="INITIAL_PAGE_SIZE"
(page)="changePage()"
[pageSizeOptions]="[25, 50, 100, 250]"
>

View File

@ -18,6 +18,7 @@ import { ProjectRolesDataSource } from './project-roles-table-datasource';
styleUrls: ['./project-roles-table.component.scss'],
})
export class ProjectRolesTableComponent implements OnInit {
public INITIAL_PAGE_SIZE: number = 50;
@Input() public projectId: string = '';
@Input() public grantId: string = '';
@Input() public disabled: boolean = false;
@ -43,41 +44,58 @@ export class ProjectRolesTableComponent implements OnInit {
}
public ngOnInit(): void {
this.dataSource.loadRoles(this.projectId, this.grantId, 0, 25, 'asc');
this.dataSource.rolesSubject.subscribe((roles) => {
const selectedRoles: Role.AsObject[] = roles.filter((role) => this.selectedKeys.includes(role.key));
this.selection.select(...selectedRoles.map((r) => r.key));
});
this.loadRolesPage();
this.selection.select(...this.selectedKeys);
this.selection.changed.subscribe(() => {
this.changedSelection.emit(this.selection.selected);
});
}
public selectAllOfGroup(group: string): void {
const groupRoles: Role.AsObject[] = this.dataSource.rolesSubject.getValue().filter((role) => role.group === group);
this.selection.select(...groupRoles.map((r) => r.key));
}
private loadRolesPage(): void {
this.dataSource.loadRoles(this.projectId, this.grantId, this.paginator?.pageIndex ?? 0, this.paginator?.pageSize ?? 25);
this.dataSource.loadRoles(
this.projectId,
this.grantId,
this.paginator?.pageIndex ?? 0,
this.paginator?.pageSize ?? this.INITIAL_PAGE_SIZE,
);
}
public changePage(): void {
this.loadRolesPage();
}
public isAllSelected(): boolean {
const numSelected = this.selection.selected.length;
const numRows = this.dataSource.totalResult;
return numSelected === numRows;
private listIsAllSelected(list: string[]): boolean {
return list.findIndex((key) => !this.selection.isSelected(key)) == -1;
}
private listIsAnySelected(list: string[]): boolean {
return list.findIndex((key) => this.selection.isSelected(key)) != -1;
}
private listMasterToggle(list: string[]): void {
if (this.listIsAllSelected(list)) this.selection.deselect(...list);
else this.selection.select(...list);
}
private compilePageKeys(): string[] {
return this.dataSource.rolesSubject.value.map((role) => role.key);
}
public masterToggle(): void {
this.isAllSelected()
? this.selection.clear()
: this.dataSource.rolesSubject.value.forEach((row: Role.AsObject) => this.selection.select(row.key));
this.listMasterToggle(this.compilePageKeys());
}
public isAllSelected(): boolean {
return this.listIsAllSelected(this.compilePageKeys());
}
public isAnySelected(): boolean {
return this.listIsAnySelected(this.compilePageKeys());
}
public groupMasterToggle(group: string): void {
this.listMasterToggle(this.dataSource.rolesSubject.value.filter((role) => role.group == group).map((role) => role.key));
}
public deleteRole(role: Role.AsObject): void {
@ -93,45 +111,28 @@ export class ProjectRolesTableComponent implements OnInit {
dialogRef.afterClosed().subscribe((resp) => {
if (resp) {
const index = this.dataSource.rolesSubject.value.findIndex((iter) => iter.key === role.key);
this.mgmtService.removeProjectRole(this.projectId, role.key).then(() => {
this.toast.showInfo('PROJECT.TOAST.ROLEREMOVED', true);
if (index > -1) {
this.dataSource.rolesSubject.value.splice(index, 1);
this.dataSource.rolesSubject.next(this.dataSource.rolesSubject.value);
}
this.loadRolesPage();
});
}
});
}
public removeRole(role: Role.AsObject, index: number): void {
this.mgmtService
.removeProjectRole(this.projectId, role.key)
.then(() => {
this.toast.showInfo('PROJECT.TOAST.ROLEREMOVED', true);
this.dataSource.rolesSubject.value.splice(index, 1);
this.dataSource.rolesSubject.next(this.dataSource.rolesSubject.value);
})
.catch((error) => {
this.toast.showError(error);
});
}
public openDetailDialog(role: Role.AsObject): void {
this.dialog.open(ProjectRoleDetailDialogComponent, {
const dialogRef = this.dialog.open(ProjectRoleDetailDialogComponent, {
data: {
role,
projectId: this.projectId,
},
width: '400px',
});
dialogRef.afterClosed().subscribe(() => this.loadRolesPage());
}
public refreshPage(): void {
this.dataSource.loadRoles(this.projectId, this.grantId, this.paginator?.pageIndex ?? 0, this.paginator?.pageSize ?? 25);
this.loadRolesPage();
}
public get selectionAllowed(): boolean {

View File

@ -4537,9 +4537,9 @@ critters@0.0.20:
pretty-bytes "^5.3.0"
cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
version "7.0.6"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
dependencies:
path-key "^3.1.0"
shebang-command "^2.0.0"
@ -6042,9 +6042,9 @@ http-proxy-agent@^5.0.0:
debug "4"
http-proxy-middleware@^2.0.3:
version "2.0.6"
resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f"
integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==
version "2.0.7"
resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz#915f236d92ae98ef48278a95dedf17e991936ec6"
integrity sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==
dependencies:
"@types/http-proxy" "^1.17.8"
http-proxy "^1.18.1"

View File

@ -48,6 +48,20 @@ func main() {
What happens here is only a target which prints out the received request, which could also be handled with a different logic.
### Check Signature
To additionally check the signature header you can add the following to the example:
```go
// validate signature
if err := actions.ValidatePayload(sentBody, req.Header.Get(actions.SigningHeader), signingKey); err != nil {
// if the signed content is not equal the sent content return an error
http.Error(w, "error", http.StatusInternalServerError)
return
}
```
Where you can replace 'signingKey' with the key received in the next step 'Create target'.
## Create target
As you see in the example above the target is created with HTTP and port '8090' and if we want to use it as webhook, the target can be created as follows:

View File

@ -64,6 +64,13 @@ There are different types of Targets:
The API documentation to create a target can be found [here](/apis/resources/action_service_v3/zitadel-actions-create-target)
### Content Signing
To ensure the integrity of request content, each call includes a 'ZITADEL-Signature' in the headers. This header contains an HMAC value computed from the request content and a timestamp, which can be used to time out requests. The logic for this process is provided in 'pkg/actions/signing.go'. The goal is to verify that the HMAC value in the header matches the HMAC value computed by the Target, ensuring that the sent and received requests are identical.
Each Target resource now contains also a Signing Key, which gets generated and returned when a Target is [created](/apis/resources/action_service_v3/zitadel-actions-create-target),
and can also be newly generated when a Target is [patched](/apis/resources/action_service_v3/zitadel-actions-patch-target).
## Execution
ZITADEL decides on specific conditions if one or more Targets have to be called.

View File

@ -0,0 +1,77 @@
<!--
query data from output.csv:
Note: you might need to adjust the WHERE clause to only filter the required trends and the current placeholders
Warning: it's currently only possible to show data of one endpoint
```
copy (SELECT
metric_name
, to_timestamp(timestamp::DOUBLE) as timestamp
, approx_quantile(metric_value, 0.50) AS p50
, approx_quantile(metric_value, 0.95) AS p95
, approx_quantile(metric_value, 0.99) AS p99
FROM
read_csv('/path/to/k6-output.csv', auto_detect=false, delim=',', quote='"', escape='"', new_line='\n', skip=0, comment='', header=true, columns={'metric_name': 'VARCHAR', 'timestamp': 'BIGINT', 'metric_value': 'DOUBLE', 'check': 'VARCHAR', 'error': 'VARCHAR', 'error_code': 'VARCHAR', 'expected_response': 'BOOLEAN', 'group': 'VARCHAR', 'method': 'VARCHAR', 'name': 'VARCHAR', 'proto': 'VARCHAR', 'scenario': 'VARCHAR', 'service': 'VARCHAR', 'status': 'BIGINT', 'subproto': 'VARCHAR', 'tls_version': 'VARCHAR', 'url': 'VARCHAR', 'extra_tags': 'VARCHAR', 'metadata': 'VARCHAR'})
WHERE
metric_name LIKE '%_duration'
GROUP BY
metric_name
, timestamp
ORDER BY
metric_name
, timestamp
) to 'output.json' (ARRAY);
```
-->
## Summary
TODO: describe the outcome of the test?
## Performance test results
| Metric | Value |
|-:-------------------------------------|-:-----|
| Baseline | none |
| Purpose | |
| Test start | UTC |
| Test duration | 30min |
| Executed test | |
| k6 version | |
| VUs | |
| Client location | |
| Client machine specification | vCPU: <br/> memory: Gb |
| ZITADEL location | |
| ZITADEL container specification | vCPU: <br/> Memory: Gb <br/>Container count: |
| ZITADEL Version | |
| ZITADEL Configuration | |
| ZITADEL feature flags | |
| Database | type: crdb / psql<br />version: |
| Database location | |
| Database specification | vCPU: <br/> memory: Gb |
| ZITADEL metrics during test | |
| Observed errors | |
| Top 3 most expensive database queries | |
| Database metrics during test | |
| k6 Iterations per second | |
| k6 overview | |
| k6 output | |
| flowchart outcome | |
## Endpoint latencies
import OutputSource from "!!raw-loader!./output.json";
import { BenchmarkChart } from '/src/components/benchmark_chart';
<BenchmarkChart testResults={OutputSource} />
## k6 output {#k6-output}
```bash
TODO: add summary of k6
```

View File

@ -0,0 +1,111 @@
---
title: Benchmarks
sidebar_label: Benchmarks
---
import DocCardList from '@theme/DocCardList';
Benchmarks are crucial to understand if ZITADEL fulfills your expected workload and what resources it needs to do so.
This document explains the process and goals of load-testing zitadel in a cloud environment.
The results can be found on sub pages.
## Goals
The primary goal is to assess if ZITADEL can scale to required proportion. The goals might change over time and maturity of ZITADEL. At the moment the goal is to assess how the applications performance scales. There are some concrete goals we have to meet:
1. [https://github.com/zitadel/zitadel/issues/8352](https://github.com/zitadel/zitadel/issues/8352) defines 1000 JWT profile auth/sec
2. [https://github.com/zitadel/zitadel/issues/4424](https://github.com/zitadel/zitadel/issues/4424) defines 1200 logins / sec.
## Procedure
First we determine the “target” of our load-test. The target is expressed as a make recipe in the load-test [Makefile](https://github.com/zitadel/zitadel/blob/main/load-test/Makefile). See also the load-test [readme](https://github.com/zitadel/zitadel/blob/main/load-test/README.md) on how to configure and run load-tests.
A target should be tested for longer periods of time, as it might take time for certain metrics to show up. For example, cloud SQL samples query insights. A runtime of at least **30 minutes** is advised at the moment.
After each iteration of load-test, we should consult the [After test procedure](#after-test-procedure) to conclude an outcome:
1. Scale
2. Log potential issuer and scale
3. Terminate testing and resolve issues
## Methodology
### Benchmark definition
Tests are implemented in the ecosystem of [k6](https://k6.io). The tests are publicly available in the [zitadel repository](https://github.com/zitadel/zitadel/tree/main/load-test). Custom extensions of k6 are implemented in the [xk6-modules repository](https://github.com/zitadel/xk6-modules).
The tests must at least measure the request duration for each API call. This gives an indication on how zitadel behaves over the duration of the load test.
### Metrics
The following metrics must be collected for each test iteration. The metrics are used to follow the decision path of the [After test procedure](https://drive.google.com/open?id=1WVr7aA8dGgV1zd2jUg1y1h_o37mkZF2O6M5Mhafn_NM):
| Metric | Type | Description | Unit |
| :---- | :---- | :---- | :---- |
| Baseline | Comparison | Defines the baseline the test is compared against. If not specified the baseline defined in this document is used. | Link to test result |
| Purpose | Description | Description what should been proved with this test run | text
| Test start | Setup | Timestamp when the test started. This is useful for gathering additional data like metrics or logs later | Date |
| Test duration | Setup | Duration of the test | Duration |
| Executed test | Setup | Name of the make recipe executed. Further information about specific test cases can be found [here](?tab=t.0#heading=h.xav4f3s5r2f3). | Name of the make recipe |
| k6 version | Setup | Version of the test client (k6) used | semantic version |
| VUs | Setup | Virtual Users which execute the test scenario in parallel | Number |
| Client location | Setup | Region or location of the machine which executed the test client. If not further specified the hoster is Google Cloud | Location / Region |
| Client machine specification | Setup | Definition of the client machine the test client ran on. The resources of the machine could be maxed out during tests therefore we collect this metric as well. The description must at least clarify the following metrics: vCPU Memory egress bandwidth | **vCPU**: Amount of threads ([additional info](https://cloud.google.com/compute/docs/cpu-platforms)) **memory**: GB **egress bandwidth**:Gbps |
| ZITADEL location | Setup | Region or location of the deployment of zitadel. If not further specified the hoster is Google Cloud | Location / Region |
| ZITADEL container specification | Setup | As ZITADEL is mainly run in cloud environments it should also be run as a container during the load tests. The description must at least clarify the following metrics: vCPU Memory egress bandwidth Scale | **vCPU**: Amount of threads ([additional info](https://cloud.google.com/compute/docs/cpu-platforms)) **memory**: GB **egress bandwidth**:Gbps **scale**: The amount of containers running during the test. The amount must not vary during the tests |
| ZITADEL Version | Setup | The version of zitadel deployed | Semantic version or commit |
| ZITADEL Configuration | Setup | Configuration of zitadel which deviates from the defaults and is not secret | yaml |
| ZITADEL feature flags | Setup | Changed feature flags | yaml |
| Database | Setup | Database type and version | **type**: crdb / psql **version**: semantic version |
| Database location | Setup | Region or location of the deployment of the database. If not further specified the hoster is Google Cloud SQL | Location / Region |
| Database specification | Setup | The description must at least clarify the following metrics: vCPU, Memory and egress bandwidth (Scale) | **vCPU**: Amount of threads ([additional info](https://cloud.google.com/compute/docs/cpu-platforms)) **memory**: GB **egress bandwidth**:Gbps **scale**: Amount of crdb nodes if crdb is used |
| ZITADEL metrics during test | Result | This metric helps understanding the bottlenecks of the executed test. At least the following metrics must be provided: CPU usage Memory usage | **CPU usage** in percent **Memory usage** in percent |
| Observed errors | Result | Errors worth mentioning, mostly unexpected errors | description |
| Top 3 most expensive database queries | Result | The execution plan of the top 3 most expensive database queries during the test execution | database execution plan |
| Database metrics during test | Result | This metric helps understanding the bottlenecks of the executed test. At least the following metrics must be provided: CPU usage Memory usage | **CPU usage** in percent **Memory usage** in percent |
| k6 Iterations per second | Result | How many test iterations were done per second | Number |
| k6 overview | Result | Shows some basic metrics aggregated over the test run At least the following metrics must be included: duration per request (min, max, avg, p50, p95, p99) VUS For simplicity just add the whole test result printed to the terminal | terminal output |
| k6 output | Result | Trends and metrics generated during the test, this contains detailed information for each step executed during each iteration | csv |
### Test setup
#### Make recipes
Details about the tests implemented can be found in [this readme](https://github.com/zitadel/zitadel/blob/main/load-test/README.md#test).
### Test conclusion
After each iteration of load-test, we should consult the [Flowchart](#after-test-procedure) to conclude an outcome:
1. [Scale](#scale)
2. [Log potential issue and scale](#potential-issues)
3. [Terminate testing](#termination) and resolve issues
#### Scale {#scale}
An outcome of scale means that the service hit some kind of resource limit, like CPU or RAM which can be increased. In such cases we increase the suggested parameter and rerun the load-test for the same target. On the next test we should analyse if the increase in scale resulted in a performance improvement proportional to the scale parameter. For example if we scale from 1 to 2 containers, it might be reasonable to expect a doubling of iterations / sec. If such an increase is not noticed, there might be another bottleneck or unlying issue, such as locking.
#### Potential issues {#potential-issues}
A potential issue has an impact on performance, but does not prevent us to scale. Such issues must be logged in GH issues and load-testing can continue. The issue can be resolved at a later time and the load-tests repeated when it is. This is primarily for issues which require big changes to ZITADEL.
#### Termination {#termination}
Scaling no longer improves iterations / second, or some kind of critical error or bug is experienced. The root cause of the issue must be resolved before we can continue with increasing scale.
### After test procedure
This flowchart shows the procedure after running a test.
![Flowchart](/img/benchmark/Flowchart.svg)
## Baseline
Will be established as soon as the goal described above is reached.
## Test results
This chapter provides a table linking to the detailed test results.
<DocCardList />

View File

@ -0,0 +1,75 @@
---
title: machine jwt profile grant benchmark of zitadel v2.65.0
sidebar_label: machine jwt profile grant
---
## Summary
Tests are halted after this test run because of too many [client read events](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/wait-event.clientread.html) on the database.
## Performance test results
| Metric | Value |
| :---- | :---- |
| Baseline | none |
| Test start | 22-10-2024 16:20 UTC |
| Test duration | 30min |
| Executed test | machine\_jwt\_profile\_grant |
| k6 version | v0.54.0 |
| VUs | 50 |
| Client location | US1 |
| Client machine specification | e2-high-cpu-4 |
| Zitadel location | US1 |
| Zitadel container specification | vCPUs: 2<br/>Memory: 512 MiB<br/>Container count: 2 |
| Zitadel feature flags | none |
| Database | postgres v15 |
| Database location | US1 |
| Database specification | vCPUs: 4<br/>Memory: 16 GiB |
| Zitadel metrics during test | |
| Observed errors | Many client read events during push |
| Top 3 most expensive database queries | 1: Query events `instance_id = $1 AND aggregate_type = $2 AND aggregate_id = $3 AND event_type = ANY($4)`<br/>2: latest sequence query during push events<br/>3: writing events during push (caused lock wait events) |
| k6 iterations per second | 193 |
| k6 overview | [output](#k6-output) |
| flowchart outcome | Halt tests, must resolve an issue |
## /token endpoint latencies
import OutputSource from "!!raw-loader!./output.json";
import { BenchmarkChart } from '/src/components/benchmark_chart';
<BenchmarkChart testResults={OutputSource} />
## k6 output {#k6-output}
```bash
checks...............................: 100.00% ✓ 695739 ✗ 0
data_received........................: 479 MB 265 kB/s
data_sent............................: 276 MB 153 kB/s
http_req_blocked.....................: min=178ns avg=5µs max=119.8ms p(50)=460ns p(95)=702ns p(99)=921ns
http_req_connecting..................: min=0s avg=1.24µs max=43.45ms p(50)=0s p(95)=0s p(99)=0s
http_req_duration....................: min=18ms avg=255.3ms max=1.22s p(50)=241.56ms p(95)=479.19ms p(99)=600.92ms
{ expected_response:true }.........: min=18ms avg=255.3ms max=1.22s p(50)=241.56ms p(95)=479.19ms p(99)=600.92ms
http_req_failed......................: 0.00% ✓ 0 ✗ 347998
http_req_receiving...................: min=25.92µs avg=536.96µs max=401.94ms p(50)=89.44µs p(95)=2.39ms p(99)=11.12ms
http_req_sending.....................: min=24.01µs avg=63.86µs max=4.48ms p(50)=60.97µs p(95)=88.69µs p(99)=141.74µs
http_req_tls_handshaking.............: min=0s avg=2.8µs max=51.05ms p(50)=0s p(95)=0s p(99)=0s
http_req_waiting.....................: min=17.65ms avg=254.7ms max=1.22s p(50)=240.88ms p(95)=478.6ms p(99)=600.6ms
http_reqs............................: 347998 192.80552/s
iteration_duration...................: min=33.86ms avg=258.77ms max=1.22s p(50)=245ms p(95)=482.61ms p(99)=604.32ms
iterations...........................: 347788 192.689171/s
login_ui_enter_login_name_duration...: min=218.61ms avg=218.61ms max=218.61ms p(50)=218.61ms p(95)=218.61ms p(99)=218.61ms
login_ui_enter_password_duration.....: min=18ms avg=18ms max=18ms p(50)=18ms p(95)=18ms p(99)=18ms
login_ui_init_login_duration.........: min=90.96ms avg=90.96ms max=90.96ms p(50)=90.96ms p(95)=90.96ms p(99)=90.96ms
login_ui_token_duration..............: min=140.02ms avg=140.02ms max=140.02ms p(50)=140.02ms p(95)=140.02ms p(99)=140.02ms
oidc_token_duration..................: min=29.85ms avg=255.38ms max=1.22s p(50)=241.61ms p(95)=479.23ms p(99)=600.95ms
org_create_org_duration..............: min=64.51ms avg=64.51ms max=64.51ms p(50)=64.51ms p(95)=64.51ms p(99)=64.51ms
user_add_machine_key_duration........: min=44.93ms avg=87.89ms max=159.52ms p(50)=84.43ms p(95)=144.59ms p(99)=155.54ms
user_create_machine_duration.........: min=65.75ms avg=266.53ms max=421.58ms p(50)=276.59ms p(95)=380.84ms p(99)=414.43ms
vus..................................: 0 min=0 max=50
vus_max..............................: 50 min=50 max=50
running (30m04.9s), 00/50 VUs, 347788 complete and 0 interrupted iterations
default ✓ [======================================] 50 VUs 30m0s
```

File diff suppressed because it is too large Load Diff

View File

@ -75,8 +75,8 @@ To install run:
```bash
flutter pub add http
flutter pub add flutter_web_auth_2
flutter pub add flutter_secure_storage
flutter pub add oidc
flutter pub add oidc_default_store
```
#### Setup for Android

View File

@ -10,7 +10,7 @@ import TokenExchangeResponse from "../../apis/openidoauth/_token_exchange_respon
The Token Exchange grant implements [RFC 8693, OAuth 2.0 Token Exchange](https://www.rfc-editor.org/rfc/rfc8693) and can be used to exchange tokens to a different scope, audience or subject. Changing the subject of an authenticated token is called impersonation or delegation. This guide will explain how token exchange is implemented inside ZITADEL and gives some usage examples.
:::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.
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.
:::
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:

View File

@ -6,7 +6,7 @@ The ZITADEL API has different possibilities to create users.
This can be used, if you are building your own registration page.
Use the following API call to create your users:
[Create User (Human)](/apis/resources/mgmt/management-service-import-human-user.api.mdx)
[Create User (Human)](apis/resources/user_service_v2/user-service-add-human-user.api.mdx)
## With Username and Password

View File

@ -0,0 +1,253 @@
---
title: Caches
sidebar_label: Caches
---
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.
:::
## Configuration
The `Caches` configuration entry defines *connectors* which can be used by several objects. It is possible to mix *connectors* with different objects based on operational needs.
```yaml
Caches:
Connectors:
SomeConnector:
Enabled: true
SomeOption: foo
SomeObject:
# Connector must be enabled above.
# When connector is empty, this cache will be disabled.
Connector: "SomeConnector"
MaxAge: 1h
LastUsage: 10m
# Log enables cache-specific logging. Default to error log to stderr when omitted.
Log:
Level: error
```
For a full configuration reference, please see the [runtime configuration file](/docs/self-hosting/manage/configure#runtime-configuration-file) section's `defaults.yaml`.
## Connectors
ZITADEL supports a number of *connectors*. Connectors integrate a cache with a storage backend. Users can combine connectors with the type of object cache depending on their operational and performance requirements.
When no connector is specified for an object cache, then no caching is performed. This is the current default.
### Auto prune
Some connectors take an `AutoPrune` option. This is provided for caches which don't have built-in expiry and cleanup routines. The auto pruner is a routine launched by ZITADEL and scans and removes outdated objects in the cache. Pruning can take a cost as they typically involve some kind of scan. However, using a long interval can cause higher storage utilization.
```yaml
Caches:
Connectors:
Memory:
Enabled: true
# AutoPrune removes invalidated or expired object from the cache.
AutoPrune:
Interval: 1m
TimeOut: 5s
```
### Redis cache
Redis is supported in simple mode. Cluster and Sentinel are not yet supported. There is also a circuit-breaker provided which prevents a single point of failure, should the single Redis instance become unavailable.
Benefits:
- Centralized cache with single source of truth
- Consistent invalidation
- Very fast when network latency is kept to a minimum
- Built-in object expiry, no pruner required
Drawbacks:
- Increased operational overhead: need to run a Redis instance as part of your infrastructure.
- When running multiple servers of ZITADEL in different regions, network roundtrip time might impact performance, neutralizing the benefit of a cache.
#### Circuit breaker
A [circuit breaker](https://learn.microsoft.com/en-us/previous-versions/msp-n-p/dn589784(v=pandp.10)?redirectedfrom=MSDN) is provided for the Redis connector, to prevent a single point of failure in the case persistent errors. When the circuit breaker opens, the cache is temporary disabled and ignored. ZITADEL will continue to operate using queries to the database.
```yaml
Caches:
Connectors:
Redis:
Enabled: true
Addr: localhost:6379
# Many other options...
CircuitBreaker:
# Interval when the counters are reset to 0.
# 0 interval never resets the counters until the CB is opened.
Interval: 0
# Amount of consecutive failures permitted
MaxConsecutiveFailures: 5
# The ratio of failed requests out of total requests
MaxFailureRatio: 0.1
# Timeout after opening of the CB, until the state is set to half-open.
Timeout: 60s
# The allowed amount of requests that are allowed to pass when the CB is half-open.
MaxRetryRequests: 1
```
### PostgreSQL cache
PostgreSQL can be used to store objects in unlogged tables. [Unlogged tables](https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-UNLOGGED) do not write to the WAL log and are therefore faster than regular tables. If the PostgreSQL server crashes, the data from those tables are lost. ZITADEL always creates the cache schema in the `zitadel` database during [setup](./updating_scaling#the-setup-phase). This connector requires a [pruner](#auto-prune) routine.
Benefits:
- Centralized cache with single source of truth
- No operational overhead. Reuses the query connection pool and the existing `zitadel` database.
- Consistent invalidation
- Faster than regular queries which often contain `JOIN` clauses.
Drawbacks:
- Slowest of the available caching options
- Might put additional strain on the database server, limiting horizontal scalability
- CockroachDB does not support unlogged tables. When this connector is enabled against CockroachDB, it does work but little to no performance benefit is to be expected.
### Local memory cache
ZITADEL is capable of caching object in local application memory, using hash-maps. Each ZITADEL server manages its own copy of the cache. This connector requires a [pruner](#auto-prune) routine.
Benefits:
- Fastest of the available caching options
- No operational overhead
Drawbacks:
- Inconsistent invalidation. An object validated in one ZITADEL server will not get invalidated in other servers.
- There's no single source of truth. Different servers may operate on a different version of an object
- Data is duplicated in each server, consuming more total memory inside a deployment.
The drawbacks restricts its usefulness in distributed deployments. However simple installations running a single server can benefit greatly from this type of cache. For example test, development or home deployments.
If inconsistency is acceptable for short periods of time, one can choose to use this type of cache in distributed deployments with short max age configuration.
**For example**: A ZITADEL deployment with 2 servers is serving 1000 req/sec total. The installation only has one instance[^1]. There is only a small amount of data cached (a few kB) so duplication is not a problem in this case. It is acceptable for [instance level setting](/docs/guides/manage/console/default-settings) to be out-dated for a short amount of time. When the memory cache is enabled for the instance objects, with a max age of 1 second, the instance only needs to be obtained from the database 2 times per second (once for each server). Saving 998 of redundant queries. Once an instance level setting is changed, it takes up to 1 second for all the servers to get the new state.
## Objects
The following section describes the type of objects ZITADEL can currently cache. Objects are actively invalidated at the cache backend when one of their properties is changed. Each object cache defines:
- `Connector`: Selects the used [connector](#connectors) back-end. Must be activated first.
- `MaxAge`: the amount of time that an object is considered valid. When this age is passed the object is ignored (cache miss) and possibly cleaned up by the [pruner](#auto-prune) or other built-in garbage collection.
- `LastUsage`: defines usage based lifetime. Each time an object is used, its usage timestamp is updated. Popular objects remain cached, while unused objects are cleaned up. This option can be used to indirectly limit the size of the cache.
- `Log`: allows specific log settings for the cache. This can be used to debug a certain cache without having to change the global log level.
```yaml
Caches:
SomeObject:
# Connector must be enabled above.
# When connector is empty, this cache will be disabled.
Connector: ""
MaxAge: 1h
LastUsage: 10m
# Log enables cache-specific logging. Default to error log to stderr when omitted.
Log:
Level: error
AddSource: true
Formatter:
Format: text
```
### Instance
All HTTP and gRPC requests sent to ZITADEL receive an instance context. The instance is usually resolved by the domain from the request. In some cases, like the [system service](/docs/apis/resources/system/system-service), the instance can be resolved by its ID. An instance object contains many of the [default settings](/docs/guides/manage/console/default-settings):
- Instance [features](/docs/guides/manage/console/default-settings#features)
- Instance domains: generated and [custom](/docs/guides/manage/cloud/instances#add-custom-domain)
- [Trusted domains](/docs/apis/resources/admin/admin-service-add-instance-trusted-domain)
- Security settings ([IFrame policy](/docs/guides/solution-scenarios/configurations#embedding-zitadel-in-an-iframe))
- Limits[^2]
- [Allowed languages](/docs/guides/manage/console/default-settings#languages)
These settings typically change infrequently in production. ***Every*** request made to ZITADEL needs to query for the instance. This is a typical case of set once, get many times where a cache can provide a significant optimization.
### Milestones
Milestones are used to track the administrator's progress in setting up their instance. Milestones are used to render *your next steps* in the [console](/docs/guides/manage/console/overview) landing page.
Milestones are reached upon the first time a certain action is performed. For example the first application created or the first human login. In order to push a "reached" event only once, ZITADEL must keep track of the current state of milestones by an eventstore query every time an eligible action is performed. This can cause an unwanted overhead on production servers, therefore they are cached.
As an extra optimization, once all milestones are reached by the instance, an in-memory flag is set and the milestone state is never queried again from the database nor cache.
For single instance setups which fulfilled all milestone (*your next steps* in console) it is not needed to enable this cache. We mainly use it for ZITADEL cloud where there are many instances with *incomplete* milestones.
### Organization
Most resources like users, project and applications are part of an [organization](/docs/concepts/structure/organizations). Therefore many parts of the ZITADEL logic search for an organization by ID or by their primary domain.
Organization objects are quite small and receive infrequent updates after they are created:
- Change of organization name
- Deactivation / Reactivation
- Change of primary domain
- Removal
## Examples
Currently caches are in beta and disabled by default. However, if you want to give caching a try, the following sections contains some suggested configurations for different setups.
The following configuration is recommended for single instance setups with a single ZITADEL server:
```yaml
Caches:
Memory:
Enabled: true
Instance:
Connector: "memory"
MaxAge: 1h
Organization:
Connector: "memory"
MaxAge: 1h
```
The following configuration is recommended for single instance setups with high traffic on multiple servers, where Redis is not available:
```yaml
Caches:
Memory:
Enabled: true
Postgres:
Enabled: true
Instance:
Connector: "memory"
MaxAge: 1s
Milestones:
Connector: "postgres"
MaxAge: 1h
LastUsage: 10m
Organization:
Connector: "memory"
MaxAge: 1s
```
When running many instances on multiple servers:
```yaml
Caches:
Connectors:
Redis:
Enabled: true
# Other connection options
Instance:
Connector: "redis"
MaxAge: 1h
LastUsage: 10m
Milestones:
Connector: "redis"
MaxAge: 1h
LastUsage: 10m
Organization:
Connector: "redis"
MaxAge: 1h
LastUsage: 10m
```
----
[^1]: Many deployments of ZITADEL have only one or few [instances](/docs/concepts/structure/instance). Multiple instances are mostly used for ZITADEL cloud, where each customer gets at least one instance.
[^2]: Limits are imposed by the system API, usually when customers exceed their subscription in ZITADEL cloud.

View File

@ -289,7 +289,7 @@ module.exports = {
outputDir: "docs/apis/resources/user_service_v2",
sidebarOptions: {
groupPathsBy: "tag",
categoryLinkSource: "tag",
categoryLinkSource: "auto",
},
},
session_v2: {
@ -297,7 +297,7 @@ module.exports = {
outputDir: "docs/apis/resources/session_service_v2",
sidebarOptions: {
groupPathsBy: "tag",
categoryLinkSource: "tag",
categoryLinkSource: "auto",
},
},
oidc_v2: {
@ -305,7 +305,7 @@ module.exports = {
outputDir: "docs/apis/resources/oidc_service_v2",
sidebarOptions: {
groupPathsBy: "tag",
categoryLinkSource: "tag",
categoryLinkSource: "auto",
},
},
settings_v2: {
@ -313,7 +313,7 @@ module.exports = {
outputDir: "docs/apis/resources/settings_service_v2",
sidebarOptions: {
groupPathsBy: "tag",
categoryLinkSource: "tag",
categoryLinkSource: "auto",
},
},
user_schema_v3: {

View File

@ -17,7 +17,7 @@
"generate:grpc": "buf generate ../proto",
"generate:apidocs": "docusaurus gen-api-docs all",
"generate:configdocs": "cp -r ../cmd/defaults.yaml ./docs/self-hosting/manage/configure/ && cp -r ../cmd/setup/steps.yaml ./docs/self-hosting/manage/configure/",
"generate:re-gen": "yarn clean-all && yarn gen-all",
"generate:re-gen": "yarn generate:clean-all && yarn generate",
"generate:clean-all": "docusaurus clean-api-docs all"
},
"dependencies": {
@ -44,6 +44,7 @@
"react": "^18.2.0",
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "^18.2.0",
"react-google-charts": "^5.2.1",
"react-player": "^2.15.1",
"sitemap": "7.1.1",
"swc-loader": "^0.2.3",

View File

@ -841,6 +841,30 @@ module.exports = {
label: "Rate Limits (Cloud)", // The link label
href: "/legal/policies/rate-limit-policy", // The internal path
},
{
type: "category",
label: "Benchmarks",
collapsed: false,
link: {
type: "doc",
id: "apis/benchmarks/index",
},
items: [
{
type: "category",
label: "v2.65.0",
link: {
title: "v2.65.0",
slug: "/apis/benchmarks/v2.65.0",
description:
"Benchmark results of Zitadel v2.65.0\n"
},
items: [
"apis/benchmarks/v2.65.0/machine_jwt_profile_grant/index",
],
},
],
},
],
selfHosting: [
{
@ -889,6 +913,7 @@ module.exports = {
"self-hosting/manage/http2",
"self-hosting/manage/tls_modes",
"self-hosting/manage/database/database",
"self-hosting/manage/cache",
"self-hosting/manage/updating_scaling",
"self-hosting/manage/usage_control",
{

View File

@ -0,0 +1,45 @@
import React from "react";
import Chart from "react-google-charts";
export function BenchmarkChart(testResults=[], height='500px') {
const options = {
legend: { position: 'bottom' },
focusTarget: 'category',
hAxis: {
title: 'timestamp',
},
vAxis: {
title: 'latency (ms)',
},
};
const data = [
[
{type:"datetime", label: "timestamp"},
{type:"number", label: "p50"},
{type:"number", label: "p95"},
{type:"number", label: "p99"},
],
]
JSON.parse(testResults.testResults).forEach((result) => {
data.push([
new Date(result.timestamp),
result.p50,
result.p95,
result.p99,
])
});
return (
<Chart
chartType="LineChart"
width="100%"
height="500px"
options={options}
data={data}
legendToggle
/>
);
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 439 KiB

View File

@ -3909,9 +3909,9 @@ cross-fetch@3.1.5:
node-fetch "2.6.7"
cross-spawn@^7.0.0, cross-spawn@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
version "7.0.6"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
dependencies:
path-key "^3.1.0"
shebang-command "^2.0.0"
@ -9479,6 +9479,11 @@ react-fast-compare@^3.0.1, react-fast-compare@^3.2.0, react-fast-compare@^3.2.2:
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49"
integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==
react-google-charts@^5.2.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/react-google-charts/-/react-google-charts-5.2.1.tgz#d9cbe8ed45d7c0fafefea5c7c3361bee76648454"
integrity sha512-mCbPiObP8yWM5A9ogej7Qp3/HX4EzOwuEzUYvcfHtL98Xt4V/brD14KgfDzSNNtyD48MNXCpq5oVaYKt0ykQUQ==
react-helmet-async@*:
version "2.0.5"
resolved "https://registry.yarnpkg.com/react-helmet-async/-/react-helmet-async-2.0.5.tgz#cfc70cd7bb32df7883a8ed55502a1513747223ec"

1
go.mod
View File

@ -56,6 +56,7 @@ require (
github.com/redis/go-redis/v9 v9.7.0
github.com/rs/cors v1.11.1
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
github.com/sony/gobreaker/v2 v2.0.0
github.com/sony/sonyflake v1.2.0
github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.19.0

2
go.sum
View File

@ -670,6 +670,8 @@ github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIK
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/sony/gobreaker/v2 v2.0.0 h1:23AaR4JQ65y4rz8JWMzgXw2gKOykZ/qfqYunll4OwJ4=
github.com/sony/gobreaker/v2 v2.0.0/go.mod h1:8JnRUz80DJ1/ne8M8v7nmTs2713i58nIt4s7XcGe/DI=
github.com/sony/sonyflake v1.2.0 h1:Pfr3A+ejSg+0SPqpoAmQgEtNDAhc2G1SUYk205qVMLQ=
github.com/sony/sonyflake v1.2.0/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=

View File

@ -42,7 +42,7 @@ func (s *Server) ExportData(ctx context.Context, req *admin_pb.ExportDataRequest
}
orgs := make([]*admin_pb.DataOrg, len(queriedOrgs.Orgs))
processedOrgs := make([]string, len(queriedOrgs.Orgs))
processedOrgs := make([]string, 0, len(queriedOrgs.Orgs))
processedProjects := make([]string, 0)
processedGrants := make([]string, 0)
processedUsers := make([]string, 0)

View File

@ -18,8 +18,6 @@ import (
)
func TestServer_GetSecurityPolicy(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
adminCtx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
@ -72,8 +70,6 @@ func TestServer_GetSecurityPolicy(t *testing.T) {
}
func TestServer_SetSecurityPolicy(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
adminCtx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)

View File

@ -21,8 +21,6 @@ import (
)
func TestServer_Restrictions_DisallowPublicOrgRegistration(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
regOrgUrl, err := url.Parse("http://" + instance.Domain + ":8080/ui/login/register/org")
require.NoError(t, err)

View File

@ -24,8 +24,6 @@ import (
)
func TestServer_Restrictions_AllowedLanguages(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
defer cancel()

View File

@ -42,8 +42,6 @@ func TestMain(m *testing.M) {
}
func TestServer_AddOrganization(t *testing.T) {
t.Parallel()
idpResp := Instance.AddGenericOAuthProvider(CTX, Instance.DefaultOrg.Id)
tests := []struct {

View File

@ -27,8 +27,6 @@ type orgAttr struct {
}
func TestServer_ListOrganizations(t *testing.T) {
t.Parallel()
type args struct {
ctx context.Context
req *org.ListOrganizationsRequest

View File

@ -26,7 +26,6 @@ import (
)
func TestServer_ExecutionTarget(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)

View File

@ -25,7 +25,6 @@ func executionTargetsSingleInclude(include *action.Condition) []*action.Executio
}
func TestServer_SetExecution_Request(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
@ -207,7 +206,6 @@ func TestServer_SetExecution_Request(t *testing.T) {
}
func TestServer_SetExecution_Request_Include(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
@ -346,7 +344,6 @@ func TestServer_SetExecution_Request_Include(t *testing.T) {
}
func TestServer_SetExecution_Response(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
@ -528,7 +525,6 @@ func TestServer_SetExecution_Response(t *testing.T) {
}
func TestServer_SetExecution_Event(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
@ -716,7 +712,6 @@ func TestServer_SetExecution_Event(t *testing.T) {
}
func TestServer_SetExecution_Function(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)

View File

@ -22,7 +22,6 @@ import (
)
func TestServer_GetTarget(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
@ -63,6 +62,7 @@ func TestServer_GetTarget(t *testing.T) {
request.Id = resp.GetDetails().GetId()
response.Target.Config.Name = name
response.Target.Details = resp.GetDetails()
response.Target.SigningKey = resp.GetSigningKey()
return nil
},
req: &action.GetTargetRequest{},
@ -93,6 +93,7 @@ func TestServer_GetTarget(t *testing.T) {
request.Id = resp.GetDetails().GetId()
response.Target.Config.Name = name
response.Target.Details = resp.GetDetails()
response.Target.SigningKey = resp.GetSigningKey()
return nil
},
req: &action.GetTargetRequest{},
@ -123,6 +124,7 @@ func TestServer_GetTarget(t *testing.T) {
request.Id = resp.GetDetails().GetId()
response.Target.Config.Name = name
response.Target.Details = resp.GetDetails()
response.Target.SigningKey = resp.GetSigningKey()
return nil
},
req: &action.GetTargetRequest{},
@ -155,6 +157,7 @@ func TestServer_GetTarget(t *testing.T) {
request.Id = resp.GetDetails().GetId()
response.Target.Config.Name = name
response.Target.Details = resp.GetDetails()
response.Target.SigningKey = resp.GetSigningKey()
return nil
},
req: &action.GetTargetRequest{},
@ -187,6 +190,7 @@ func TestServer_GetTarget(t *testing.T) {
request.Id = resp.GetDetails().GetId()
response.Target.Config.Name = name
response.Target.Details = resp.GetDetails()
response.Target.SigningKey = resp.GetSigningKey()
return nil
},
req: &action.GetTargetRequest{},
@ -231,13 +235,13 @@ func TestServer_GetTarget(t *testing.T) {
gotTarget := got.GetTarget()
integration.AssertResourceDetails(ttt, wantTarget.GetDetails(), gotTarget.GetDetails())
assert.EqualExportedValues(ttt, wantTarget.GetConfig(), gotTarget.GetConfig())
assert.Equal(ttt, wantTarget.GetSigningKey(), gotTarget.GetSigningKey())
}, retryDuration, tick, "timeout waiting for expected target result")
})
}
}
func TestServer_ListTargets(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
@ -494,6 +498,7 @@ func TestServer_ListTargets(t *testing.T) {
for i := range tt.want.Result {
integration.AssertResourceDetails(ttt, tt.want.Result[i].GetDetails(), got.Result[i].GetDetails())
assert.EqualExportedValues(ttt, tt.want.Result[i].GetConfig(), got.Result[i].GetConfig())
assert.NotEmpty(ttt, got.Result[i].GetSigningKey())
}
}
integration.AssertResourceListDetails(ttt, tt.want, got)
@ -503,7 +508,6 @@ func TestServer_ListTargets(t *testing.T) {
}
func TestServer_SearchExecutions(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)

View File

@ -9,6 +9,7 @@ import (
"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/durationpb"
"google.golang.org/protobuf/types/known/timestamppb"
@ -21,7 +22,6 @@ import (
)
func TestServer_CreateTarget(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
@ -201,11 +201,12 @@ func TestServer_CreateTarget(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
got, err := instance.Client.ActionV3Alpha.CreateTarget(tt.ctx, &action.CreateTargetRequest{Target: tt.req})
if tt.wantErr {
require.Error(t, err)
return
assert.Error(t, err)
} else {
assert.NoError(t, err)
integration.AssertResourceDetails(t, tt.want, got.Details)
assert.NotEmpty(t, got.GetSigningKey())
}
require.NoError(t, err)
integration.AssertResourceDetails(t, tt.want, got.Details)
})
}
}
@ -218,11 +219,15 @@ func TestServer_PatchTarget(t *testing.T) {
ctx context.Context
req *action.PatchTargetRequest
}
type want struct {
details *resource_object.Details
signingKey bool
}
tests := []struct {
name string
prepare func(request *action.PatchTargetRequest) error
args args
want *resource_object.Details
want want
wantErr bool
}{
{
@ -273,14 +278,42 @@ func TestServer_PatchTarget(t *testing.T) {
},
},
},
want: &resource_object.Details{
Changed: timestamppb.Now(),
Owner: &object.Owner{
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
Id: instance.ID(),
want: want{
details: &resource_object.Details{
Changed: timestamppb.Now(),
Owner: &object.Owner{
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
Id: instance.ID(),
},
},
},
},
{
name: "regenerate signingkey, ok",
prepare: func(request *action.PatchTargetRequest) error {
targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId()
request.Id = targetID
return nil
},
args: args{
ctx: isolatedIAMOwnerCTX,
req: &action.PatchTargetRequest{
Target: &action.PatchTarget{
ExpirationSigningKey: durationpb.New(0 * time.Second),
},
},
},
want: want{
details: &resource_object.Details{
Changed: timestamppb.Now(),
Owner: &object.Owner{
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
Id: instance.ID(),
},
},
signingKey: true,
},
},
{
name: "change type, ok",
prepare: func(request *action.PatchTargetRequest) error {
@ -300,11 +333,13 @@ func TestServer_PatchTarget(t *testing.T) {
},
},
},
want: &resource_object.Details{
Changed: timestamppb.Now(),
Owner: &object.Owner{
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
Id: instance.ID(),
want: want{
details: &resource_object.Details{
Changed: timestamppb.Now(),
Owner: &object.Owner{
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
Id: instance.ID(),
},
},
},
},
@ -323,11 +358,13 @@ func TestServer_PatchTarget(t *testing.T) {
},
},
},
want: &resource_object.Details{
Changed: timestamppb.Now(),
Owner: &object.Owner{
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
Id: instance.ID(),
want: want{
details: &resource_object.Details{
Changed: timestamppb.Now(),
Owner: &object.Owner{
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
Id: instance.ID(),
},
},
},
},
@ -346,11 +383,13 @@ func TestServer_PatchTarget(t *testing.T) {
},
},
},
want: &resource_object.Details{
Changed: timestamppb.Now(),
Owner: &object.Owner{
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
Id: instance.ID(),
want: want{
details: &resource_object.Details{
Changed: timestamppb.Now(),
Owner: &object.Owner{
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
Id: instance.ID(),
},
},
},
},
@ -371,11 +410,13 @@ func TestServer_PatchTarget(t *testing.T) {
},
},
},
want: &resource_object.Details{
Changed: timestamppb.Now(),
Owner: &object.Owner{
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
Id: instance.ID(),
want: want{
details: &resource_object.Details{
Changed: timestamppb.Now(),
Owner: &object.Owner{
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
Id: instance.ID(),
},
},
},
},
@ -388,11 +429,14 @@ func TestServer_PatchTarget(t *testing.T) {
instance.Client.ActionV3Alpha.PatchTarget(tt.args.ctx, tt.args.req)
got, err := instance.Client.ActionV3Alpha.PatchTarget(tt.args.ctx, tt.args.req)
if tt.wantErr {
require.Error(t, err)
return
assert.Error(t, err)
} else {
assert.NoError(t, err)
integration.AssertResourceDetails(t, tt.want.details, got.Details)
if tt.want.signingKey {
assert.NotEmpty(t, got.SigningKey)
}
}
require.NoError(t, err)
integration.AssertResourceDetails(t, tt.want, got.Details)
})
}
}
@ -444,11 +488,12 @@ func TestServer_DeleteTarget(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
got, err := instance.Client.ActionV3Alpha.DeleteTarget(tt.ctx, tt.req)
if tt.wantErr {
require.Error(t, err)
assert.Error(t, err)
return
} else {
assert.NoError(t, err)
integration.AssertResourceDetails(t, tt.want, got.Details)
}
require.NoError(t, err)
integration.AssertResourceDetails(t, tt.want, got.Details)
})
}
}

View File

@ -97,6 +97,7 @@ func targetToPb(t *query.Target) *action.GetTarget {
Timeout: durationpb.New(t.Timeout),
Endpoint: t.Endpoint,
},
SigningKey: t.SigningKey,
}
switch t.TargetType {
case domain.TargetTypeWebhook:

View File

@ -25,7 +25,8 @@ func (s *Server) CreateTarget(ctx context.Context, req *action.CreateTargetReque
return nil, err
}
return &action.CreateTargetResponse{
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_INSTANCE, instanceID),
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_INSTANCE, instanceID),
SigningKey: add.SigningKey,
}, nil
}
@ -34,12 +35,14 @@ func (s *Server) PatchTarget(ctx context.Context, req *action.PatchTargetRequest
return nil, err
}
instanceID := authz.GetInstance(ctx).InstanceID()
details, err := s.command.ChangeTarget(ctx, patchTargetToCommand(req), instanceID)
patch := patchTargetToCommand(req)
details, err := s.command.ChangeTarget(ctx, patch, instanceID)
if err != nil {
return nil, err
}
return &action.PatchTargetResponse{
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_INSTANCE, instanceID),
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_INSTANCE, instanceID),
SigningKey: patch.SigningKey,
}, nil
}
@ -83,6 +86,12 @@ func createTargetToCommand(req *action.CreateTargetRequest) *command.AddTarget {
}
func patchTargetToCommand(req *action.PatchTargetRequest) *command.ChangeTarget {
expirationSigningKey := false
// TODO handle expiration, currently only immediate expiration is supported
if req.GetTarget().GetExpirationSigningKey() != nil {
expirationSigningKey = true
}
reqTarget := req.GetTarget()
if reqTarget == nil {
return nil
@ -91,8 +100,9 @@ func patchTargetToCommand(req *action.PatchTargetRequest) *command.ChangeTarget
ObjectRoot: models.ObjectRoot{
AggregateID: req.GetId(),
},
Name: reqTarget.Name,
Endpoint: reqTarget.Endpoint,
Name: reqTarget.Name,
Endpoint: reqTarget.Endpoint,
ExpirationSigningKey: expirationSigningKey,
}
if reqTarget.TargetType != nil {
switch t := reqTarget.GetTargetType().(type) {

View File

@ -19,7 +19,6 @@ import (
)
func TestServer_SetContactEmail(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
@ -365,7 +364,6 @@ func TestServer_SetContactEmail(t *testing.T) {
}
func TestServer_VerifyContactEmail(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
@ -555,7 +553,6 @@ func TestServer_VerifyContactEmail(t *testing.T) {
}
func TestServer_ResendContactEmailCode(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)

View File

@ -18,7 +18,6 @@ import (
)
func TestServer_SetContactPhone(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
@ -292,7 +291,6 @@ func TestServer_SetContactPhone(t *testing.T) {
}
func TestServer_VerifyContactPhone(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
@ -484,7 +482,6 @@ func TestServer_VerifyContactPhone(t *testing.T) {
}
func TestServer_ResendContactPhoneCode(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)

View File

@ -21,7 +21,6 @@ import (
)
func TestServer_CreateUser(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
@ -230,7 +229,6 @@ func TestServer_CreateUser(t *testing.T) {
}
func TestServer_PatchUser(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
@ -650,7 +648,6 @@ func TestServer_PatchUser(t *testing.T) {
}
func TestServer_DeleteUser(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
@ -868,7 +865,6 @@ func unmarshalJSON(data string) *structpb.Struct {
}
func TestServer_LockUser(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
@ -1070,7 +1066,6 @@ func TestServer_LockUser(t *testing.T) {
}
func TestServer_UnlockUser(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
@ -1253,7 +1248,6 @@ func TestServer_UnlockUser(t *testing.T) {
}
func TestServer_DeactivateUser(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
@ -1455,7 +1449,6 @@ func TestServer_DeactivateUser(t *testing.T) {
}
func TestServer_ActivateUser(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)

View File

@ -20,7 +20,6 @@ import (
)
func TestServer_ListUserSchemas(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
@ -214,7 +213,6 @@ func TestServer_ListUserSchemas(t *testing.T) {
}
func TestServer_GetUserSchema(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)

View File

@ -19,7 +19,6 @@ import (
)
func TestServer_CreateUserSchema(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
@ -304,7 +303,6 @@ func TestServer_CreateUserSchema(t *testing.T) {
}
func TestServer_UpdateUserSchema(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
@ -596,7 +594,6 @@ func TestServer_UpdateUserSchema(t *testing.T) {
}
func TestServer_DeactivateUserSchema(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
@ -678,7 +675,6 @@ func TestServer_DeactivateUserSchema(t *testing.T) {
}
func TestServer_ReactivateUserSchema(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
@ -760,7 +756,6 @@ func TestServer_ReactivateUserSchema(t *testing.T) {
}
func TestServer_DeleteUserSchema(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)

View File

@ -26,6 +26,7 @@ type mockExecutionTarget struct {
Endpoint string
Timeout time.Duration
InterruptOnError bool
SigningKey string
}
func (e *mockExecutionTarget) SetEndpoint(endpoint string) {
@ -49,6 +50,9 @@ func (e *mockExecutionTarget) GetTargetID() string {
func (e *mockExecutionTarget) GetExecutionID() string {
return e.ExecutionID
}
func (e *mockExecutionTarget) GetSigningKey() string {
return e.SigningKey
}
type mockContentRequest struct {
Content string
@ -157,6 +161,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) {
TargetID: "target",
TargetType: domain.TargetTypeCall,
Timeout: time.Minute,
SigningKey: "signingkey",
},
},
targets: []target{
@ -186,6 +191,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) {
TargetType: domain.TargetTypeCall,
Timeout: time.Minute,
InterruptOnError: true,
SigningKey: "signingkey",
},
},
@ -216,6 +222,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) {
TargetType: domain.TargetTypeCall,
Timeout: time.Second,
InterruptOnError: true,
SigningKey: "signingkey",
},
},
targets: []target{
@ -245,6 +252,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) {
TargetType: domain.TargetTypeCall,
Timeout: time.Second,
InterruptOnError: true,
SigningKey: "signingkey",
},
},
targets: []target{
@ -269,6 +277,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) {
TargetType: domain.TargetTypeCall,
Timeout: time.Minute,
InterruptOnError: true,
SigningKey: "signingkey",
},
},
targets: []target{
@ -297,6 +306,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) {
TargetID: "target",
TargetType: domain.TargetTypeAsync,
Timeout: time.Second,
SigningKey: "signingkey",
},
},
targets: []target{
@ -325,6 +335,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) {
TargetID: "target",
TargetType: domain.TargetTypeAsync,
Timeout: time.Minute,
SigningKey: "signingkey",
},
},
targets: []target{
@ -354,6 +365,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) {
TargetType: domain.TargetTypeWebhook,
Timeout: time.Minute,
InterruptOnError: true,
SigningKey: "signingkey",
},
},
targets: []target{
@ -382,6 +394,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) {
TargetType: domain.TargetTypeWebhook,
Timeout: time.Second,
InterruptOnError: true,
SigningKey: "signingkey",
},
},
targets: []target{
@ -411,6 +424,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) {
TargetType: domain.TargetTypeWebhook,
Timeout: time.Minute,
InterruptOnError: true,
SigningKey: "signingkey",
},
},
targets: []target{
@ -440,6 +454,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) {
TargetType: domain.TargetTypeCall,
Timeout: time.Minute,
InterruptOnError: true,
SigningKey: "signingkey",
},
&mockExecutionTarget{
InstanceID: "instance",
@ -448,6 +463,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) {
TargetType: domain.TargetTypeCall,
Timeout: time.Minute,
InterruptOnError: true,
SigningKey: "signingkey",
},
&mockExecutionTarget{
InstanceID: "instance",
@ -456,6 +472,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) {
TargetType: domain.TargetTypeCall,
Timeout: time.Minute,
InterruptOnError: true,
SigningKey: "signingkey",
},
},
@ -498,6 +515,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) {
TargetType: domain.TargetTypeCall,
Timeout: time.Minute,
InterruptOnError: true,
SigningKey: "signingkey",
},
&mockExecutionTarget{
InstanceID: "instance",
@ -506,6 +524,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) {
TargetType: domain.TargetTypeCall,
Timeout: time.Second,
InterruptOnError: true,
SigningKey: "signingkey",
},
&mockExecutionTarget{
InstanceID: "instance",
@ -514,6 +533,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) {
TargetType: domain.TargetTypeCall,
Timeout: time.Second,
InterruptOnError: true,
SigningKey: "signingkey",
},
},
targets: []target{
@ -692,6 +712,7 @@ func Test_executeTargetsForGRPCFullMethod_response(t *testing.T) {
TargetType: domain.TargetTypeCall,
Timeout: time.Minute,
InterruptOnError: true,
SigningKey: "signingkey",
},
},
targets: []target{
@ -721,6 +742,7 @@ func Test_executeTargetsForGRPCFullMethod_response(t *testing.T) {
TargetType: domain.TargetTypeCall,
Timeout: time.Minute,
InterruptOnError: true,
SigningKey: "signingkey",
},
},
targets: []target{

View File

@ -15,8 +15,6 @@ import (
)
func TestServer_ListInstances(t *testing.T) {
t.Parallel()
isoInstance := integration.NewInstance(CTX)
tests := []struct {

View File

@ -22,8 +22,6 @@ import (
)
func TestServer_Limits_AuditLogRetention(t *testing.T) {
t.Parallel()
isoInstance := integration.NewInstance(CTX)
iamOwnerCtx := isoInstance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
userID, projectID, appID, projectGrantID := seedObjects(iamOwnerCtx, t, isoInstance.Client)

View File

@ -26,8 +26,6 @@ import (
)
func TestServer_Limits_Block(t *testing.T) {
t.Parallel()
isoInstance := integration.NewInstance(CTX)
iamOwnerCtx := isoInstance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
tests := []*test{

View File

@ -17,8 +17,6 @@ import (
)
func TestServer_SetEmail(t *testing.T) {
t.Parallel()
userID := Instance.CreateHumanUser(CTX).GetUserId()
tests := []struct {
@ -148,8 +146,6 @@ func TestServer_SetEmail(t *testing.T) {
}
func TestServer_ResendEmailCode(t *testing.T) {
t.Parallel()
userID := Instance.CreateHumanUser(CTX).GetUserId()
verifiedUserID := Instance.CreateHumanUserVerified(CTX, Instance.DefaultOrg.Id, gofakeit.Email()).GetUserId()
@ -254,8 +250,6 @@ func TestServer_ResendEmailCode(t *testing.T) {
}
func TestServer_VerifyEmail(t *testing.T) {
t.Parallel()
userResp := Instance.CreateHumanUser(CTX)
tests := []struct {
name string

View File

@ -20,8 +20,6 @@ import (
)
func TestServer_AddIDPLink(t *testing.T) {
t.Parallel()
idpResp := Instance.AddGenericOAuthProvider(IamCTX, Instance.DefaultOrg.Id)
type args struct {
ctx context.Context
@ -101,8 +99,6 @@ func TestServer_AddIDPLink(t *testing.T) {
}
func TestServer_ListIDPLinks(t *testing.T) {
t.Parallel()
orgResp := Instance.CreateOrganization(IamCTX, fmt.Sprintf("ListIDPLinks-%s", gofakeit.AppName()), gofakeit.Email())
instanceIdpResp := Instance.AddGenericOAuthProvider(IamCTX, Instance.DefaultOrg.Id)
@ -257,8 +253,6 @@ func TestServer_ListIDPLinks(t *testing.T) {
}
func TestServer_RemoveIDPLink(t *testing.T) {
t.Parallel()
orgResp := Instance.CreateOrganization(IamCTX, fmt.Sprintf("ListIDPLinks-%s", gofakeit.AppName()), gofakeit.Email())
instanceIdpResp := Instance.AddGenericOAuthProvider(IamCTX, Instance.DefaultOrg.Id)

View File

@ -15,8 +15,6 @@ import (
)
func TestServer_AddOTPSMS(t *testing.T) {
t.Parallel()
userID := Instance.CreateHumanUser(CTX).GetUserId()
Instance.RegisterUserPasskey(CTX, userID)
_, sessionToken, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userID)
@ -123,8 +121,6 @@ func TestServer_AddOTPSMS(t *testing.T) {
}
func TestServer_RemoveOTPSMS(t *testing.T) {
t.Parallel()
userID := Instance.CreateHumanUser(CTX).GetUserId()
Instance.RegisterUserPasskey(CTX, userID)
_, sessionToken, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userID)
@ -191,8 +187,6 @@ func TestServer_RemoveOTPSMS(t *testing.T) {
}
func TestServer_AddOTPEmail(t *testing.T) {
t.Parallel()
userID := Instance.CreateHumanUser(CTX).GetUserId()
Instance.RegisterUserPasskey(CTX, userID)
_, sessionToken, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userID)
@ -301,8 +295,6 @@ func TestServer_AddOTPEmail(t *testing.T) {
}
func TestServer_RemoveOTPEmail(t *testing.T) {
t.Parallel()
userID := Instance.CreateHumanUser(CTX).GetUserId()
Instance.RegisterUserPasskey(CTX, userID)
_, sessionToken, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userID)

View File

@ -19,8 +19,6 @@ import (
)
func TestServer_RegisterPasskey(t *testing.T) {
t.Parallel()
userID := Instance.CreateHumanUser(CTX).GetUserId()
reg, err := Client.CreatePasskeyRegistrationLink(CTX, &user.CreatePasskeyRegistrationLinkRequest{
UserId: userID,
@ -141,8 +139,6 @@ func TestServer_RegisterPasskey(t *testing.T) {
}
func TestServer_VerifyPasskeyRegistration(t *testing.T) {
t.Parallel()
userID, pkr := userWithPasskeyRegistered(t)
attestationResponse, err := Instance.WebAuthN.CreateAttestationResponse(pkr.GetPublicKeyCredentialCreationOptions())
@ -219,8 +215,6 @@ func TestServer_VerifyPasskeyRegistration(t *testing.T) {
}
func TestServer_CreatePasskeyRegistrationLink(t *testing.T) {
t.Parallel()
userID := Instance.CreateHumanUser(CTX).GetUserId()
type args struct {
@ -354,8 +348,6 @@ func passkeyVerify(t *testing.T, userID string, pkr *user.RegisterPasskeyRespons
}
func TestServer_RemovePasskey(t *testing.T) {
t.Parallel()
userIDWithout := Instance.CreateHumanUser(CTX).GetUserId()
userIDRegistered, pkrRegistered := userWithPasskeyRegistered(t)
userIDVerified, passkeyIDVerified := userWithPasskeyVerified(t)
@ -461,8 +453,6 @@ func TestServer_RemovePasskey(t *testing.T) {
}
func TestServer_ListPasskeys(t *testing.T) {
t.Parallel()
userIDWithout := Instance.CreateHumanUser(CTX).GetUserId()
userIDRegistered, _ := userWithPasskeyRegistered(t)
userIDVerified, passkeyIDVerified := userWithPasskeyVerified(t)

View File

@ -17,8 +17,6 @@ import (
)
func TestServer_RequestPasswordReset(t *testing.T) {
t.Parallel()
userID := Instance.CreateHumanUser(CTX).GetUserId()
tests := []struct {
@ -107,8 +105,6 @@ func TestServer_RequestPasswordReset(t *testing.T) {
}
func TestServer_SetPassword(t *testing.T) {
t.Parallel()
type args struct {
ctx context.Context
req *user.SetPasswordRequest

View File

@ -18,8 +18,6 @@ import (
)
func TestServer_SetPhone(t *testing.T) {
t.Parallel()
userID := Instance.CreateHumanUser(CTX).GetUserId()
tests := []struct {
@ -124,8 +122,6 @@ func TestServer_SetPhone(t *testing.T) {
}
func TestServer_ResendPhoneCode(t *testing.T) {
t.Parallel()
userID := Instance.CreateHumanUser(CTX).GetUserId()
verifiedUserID := Instance.CreateHumanUserVerified(CTX, Instance.DefaultOrg.Id, gofakeit.Email()).GetUserId()
@ -201,8 +197,6 @@ func TestServer_ResendPhoneCode(t *testing.T) {
}
func TestServer_VerifyPhone(t *testing.T) {
t.Parallel()
userResp := Instance.CreateHumanUser(CTX)
tests := []struct {
name string
@ -256,8 +250,6 @@ func TestServer_VerifyPhone(t *testing.T) {
}
func TestServer_RemovePhone(t *testing.T) {
t.Parallel()
userResp := Instance.CreateHumanUser(CTX)
failResp := Instance.CreateHumanUserNoPhone(CTX)
otherUser := Instance.CreateHumanUser(CTX).GetUserId()

View File

@ -20,8 +20,6 @@ import (
)
func TestServer_GetUserByID(t *testing.T) {
t.Parallel()
orgResp := Instance.CreateOrganization(IamCTX, fmt.Sprintf("GetUserByIDOrg-%s", gofakeit.AppName()), gofakeit.Email())
type args struct {
ctx context.Context
@ -188,8 +186,6 @@ func TestServer_GetUserByID(t *testing.T) {
}
func TestServer_GetUserByID_Permission(t *testing.T) {
t.Parallel()
newOrgOwnerEmail := gofakeit.Email()
newOrg := Instance.CreateOrganization(IamCTX, fmt.Sprintf("GetHuman-%s", gofakeit.AppName()), newOrgOwnerEmail)
newUserID := newOrg.CreatedAdmins[0].GetUserId()
@ -337,8 +333,6 @@ type userAttr struct {
}
func TestServer_ListUsers(t *testing.T) {
t.Parallel()
orgResp := Instance.CreateOrganization(IamCTX, fmt.Sprintf("ListUsersOrg-%s", gofakeit.AppName()), gofakeit.Email())
userResp := Instance.CreateHumanUserVerified(IamCTX, orgResp.OrganizationId, gofakeit.Email())
type args struct {

View File

@ -18,8 +18,6 @@ import (
)
func TestServer_RegisterTOTP(t *testing.T) {
t.Parallel()
userID := Instance.CreateHumanUser(CTX).GetUserId()
Instance.RegisterUserPasskey(CTX, userID)
_, sessionToken, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userID)
@ -106,8 +104,6 @@ func TestServer_RegisterTOTP(t *testing.T) {
}
func TestServer_VerifyTOTPRegistration(t *testing.T) {
t.Parallel()
userID := Instance.CreateHumanUser(CTX).GetUserId()
Instance.RegisterUserPasskey(CTX, userID)
_, sessionToken, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userID)
@ -211,8 +207,6 @@ func TestServer_VerifyTOTPRegistration(t *testing.T) {
}
func TestServer_RemoveTOTP(t *testing.T) {
t.Parallel()
userID := Instance.CreateHumanUser(CTX).GetUserId()
Instance.RegisterUserPasskey(CTX, userID)
_, sessionToken, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userID)

View File

@ -17,8 +17,6 @@ import (
)
func TestServer_RegisterU2F(t *testing.T) {
t.Parallel()
userID := Instance.CreateHumanUser(CTX).GetUserId()
otherUser := Instance.CreateHumanUser(CTX).GetUserId()
@ -108,8 +106,6 @@ func TestServer_RegisterU2F(t *testing.T) {
}
func TestServer_VerifyU2FRegistration(t *testing.T) {
t.Parallel()
ctx, userID, pkr := ctxFromNewUserWithRegisteredU2F(t)
attestationResponse, err := Instance.WebAuthN.CreateAttestationResponse(pkr.GetPublicKeyCredentialCreationOptions())
@ -215,8 +211,6 @@ func ctxFromNewUserWithVerifiedU2F(t *testing.T) (context.Context, string, strin
}
func TestServer_RemoveU2F(t *testing.T) {
t.Parallel()
userIDWithout := Instance.CreateHumanUser(CTX).GetUserId()
ctxRegistered, userIDRegistered, pkrRegistered := ctxFromNewUserWithRegisteredU2F(t)
_, userIDVerified, u2fVerified := ctxFromNewUserWithVerifiedU2F(t)

View File

@ -53,8 +53,6 @@ func TestMain(m *testing.M) {
}
func TestServer_AddHumanUser(t *testing.T) {
t.Parallel()
idpResp := Instance.AddGenericOAuthProvider(IamCTX, Instance.DefaultOrg.Id)
type args struct {
ctx context.Context
@ -681,8 +679,6 @@ func TestServer_AddHumanUser(t *testing.T) {
}
func TestServer_AddHumanUser_Permission(t *testing.T) {
t.Parallel()
newOrgOwnerEmail := gofakeit.Email()
newOrg := Instance.CreateOrganization(IamCTX, fmt.Sprintf("AddHuman-%s", gofakeit.AppName()), newOrgOwnerEmail)
type args struct {
@ -876,8 +872,6 @@ func TestServer_AddHumanUser_Permission(t *testing.T) {
}
func TestServer_UpdateHumanUser(t *testing.T) {
t.Parallel()
type args struct {
ctx context.Context
req *user.UpdateHumanUserRequest
@ -1239,8 +1233,6 @@ func TestServer_UpdateHumanUser(t *testing.T) {
}
func TestServer_UpdateHumanUser_Permission(t *testing.T) {
t.Parallel()
newOrgOwnerEmail := gofakeit.Email()
newOrg := Instance.CreateOrganization(IamCTX, fmt.Sprintf("UpdateHuman-%s", gofakeit.AppName()), newOrgOwnerEmail)
newUserID := newOrg.CreatedAdmins[0].GetUserId()
@ -1324,8 +1316,6 @@ func TestServer_UpdateHumanUser_Permission(t *testing.T) {
}
func TestServer_LockUser(t *testing.T) {
t.Parallel()
type args struct {
ctx context.Context
req *user.LockUserRequest
@ -1434,8 +1424,6 @@ func TestServer_LockUser(t *testing.T) {
}
func TestServer_UnLockUser(t *testing.T) {
t.Parallel()
type args struct {
ctx context.Context
req *user.UnlockUserRequest
@ -1544,8 +1532,6 @@ func TestServer_UnLockUser(t *testing.T) {
}
func TestServer_DeactivateUser(t *testing.T) {
t.Parallel()
type args struct {
ctx context.Context
req *user.DeactivateUserRequest
@ -1655,8 +1641,6 @@ func TestServer_DeactivateUser(t *testing.T) {
}
func TestServer_ReactivateUser(t *testing.T) {
t.Parallel()
type args struct {
ctx context.Context
req *user.ReactivateUserRequest
@ -1765,8 +1749,6 @@ func TestServer_ReactivateUser(t *testing.T) {
}
func TestServer_DeleteUser(t *testing.T) {
t.Parallel()
projectResp, err := Instance.CreateProject(CTX)
require.NoError(t, err)
type args struct {
@ -1866,8 +1848,6 @@ func TestServer_DeleteUser(t *testing.T) {
}
func TestServer_StartIdentityProviderIntent(t *testing.T) {
t.Parallel()
idpResp := Instance.AddGenericOAuthProvider(IamCTX, Instance.DefaultOrg.Id)
orgIdpResp := Instance.AddOrgGenericOAuthProvider(CTX, Instance.DefaultOrg.Id)
orgResp := Instance.CreateOrganization(IamCTX, fmt.Sprintf("NotDefaultOrg-%s", gofakeit.AppName()), gofakeit.Email())
@ -2131,9 +2111,7 @@ func TestServer_StartIdentityProviderIntent(t *testing.T) {
/*
func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
t.Parallel()
idpID := Instance.AddGenericOAuthProvider(t, CTX)
idpID := Instance.AddGenericOAuthProvider(t, CTX)
intentID := Instance.CreateIntent(t, CTX, idpID)
successfulID, token, changeDate, sequence := Instance.CreateSuccessfulOAuthIntent(t, CTX, idpID, "", "id")
successfulWithUserID, withUsertoken, withUserchangeDate, withUsersequence := Instance.CreateSuccessfulOAuthIntent(t, CTX, idpID, "user", "id")
@ -2421,8 +2399,6 @@ func ctxFromNewUserWithVerifiedPasswordlessLegacy(t *testing.T) (context.Context
}
func TestServer_ListAuthenticationMethodTypes(t *testing.T) {
t.Parallel()
userIDWithoutAuth := Instance.CreateHumanUser(CTX).GetUserId()
userIDWithPasskey := Instance.CreateHumanUser(CTX).GetUserId()
@ -2654,8 +2630,6 @@ func TestServer_ListAuthenticationMethodTypes(t *testing.T) {
}
func TestServer_CreateInviteCode(t *testing.T) {
t.Parallel()
type args struct {
ctx context.Context
req *user.CreateInviteCodeRequest
@ -2787,8 +2761,6 @@ func TestServer_CreateInviteCode(t *testing.T) {
}
func TestServer_ResendInviteCode(t *testing.T) {
t.Parallel()
type args struct {
ctx context.Context
req *user.ResendInviteCodeRequest
@ -2878,8 +2850,6 @@ func TestServer_ResendInviteCode(t *testing.T) {
}
func TestServer_VerifyInviteCode(t *testing.T) {
t.Parallel()
type args struct {
ctx context.Context
req *user.VerifyInviteCodeRequest

View File

@ -17,8 +17,6 @@ import (
)
func TestServer_SetEmail(t *testing.T) {
t.Parallel()
userID := Instance.CreateHumanUser(CTX).GetUserId()
tests := []struct {
@ -148,8 +146,6 @@ func TestServer_SetEmail(t *testing.T) {
}
func TestServer_ResendEmailCode(t *testing.T) {
t.Parallel()
userID := Instance.CreateHumanUser(CTX).GetUserId()
verifiedUserID := Instance.CreateHumanUserVerified(CTX, Instance.DefaultOrg.Id, gofakeit.Email()).GetUserId()
@ -254,8 +250,6 @@ func TestServer_ResendEmailCode(t *testing.T) {
}
func TestServer_VerifyEmail(t *testing.T) {
t.Parallel()
userResp := Instance.CreateHumanUser(CTX)
tests := []struct {
name string

View File

@ -15,8 +15,6 @@ import (
)
func TestServer_AddOTPSMS(t *testing.T) {
t.Parallel()
userID := Instance.CreateHumanUser(CTX).GetUserId()
Instance.RegisterUserPasskey(CTX, userID)
_, sessionToken, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userID)
@ -123,8 +121,6 @@ func TestServer_AddOTPSMS(t *testing.T) {
}
func TestServer_RemoveOTPSMS(t *testing.T) {
t.Parallel()
userID := Instance.CreateHumanUser(CTX).GetUserId()
Instance.RegisterUserPasskey(CTX, userID)
_, sessionToken, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userID)
@ -191,8 +187,6 @@ func TestServer_RemoveOTPSMS(t *testing.T) {
}
func TestServer_AddOTPEmail(t *testing.T) {
t.Parallel()
userID := Instance.CreateHumanUser(CTX).GetUserId()
Instance.RegisterUserPasskey(CTX, userID)
_, sessionToken, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userID)
@ -301,8 +295,6 @@ func TestServer_AddOTPEmail(t *testing.T) {
}
func TestServer_RemoveOTPEmail(t *testing.T) {
t.Parallel()
userID := Instance.CreateHumanUser(CTX).GetUserId()
Instance.RegisterUserPasskey(CTX, userID)
_, sessionToken, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userID)

View File

@ -18,8 +18,6 @@ import (
)
func TestServer_RegisterPasskey(t *testing.T) {
t.Parallel()
userID := Instance.CreateHumanUser(CTX).GetUserId()
reg, err := Client.CreatePasskeyRegistrationLink(CTX, &user.CreatePasskeyRegistrationLinkRequest{
UserId: userID,
@ -140,8 +138,6 @@ func TestServer_RegisterPasskey(t *testing.T) {
}
func TestServer_VerifyPasskeyRegistration(t *testing.T) {
t.Parallel()
userID := Instance.CreateHumanUser(CTX).GetUserId()
reg, err := Client.CreatePasskeyRegistrationLink(CTX, &user.CreatePasskeyRegistrationLinkRequest{
UserId: userID,
@ -230,8 +226,6 @@ func TestServer_VerifyPasskeyRegistration(t *testing.T) {
}
func TestServer_CreatePasskeyRegistrationLink(t *testing.T) {
t.Parallel()
userID := Instance.CreateHumanUser(CTX).GetUserId()
type args struct {

View File

@ -17,8 +17,6 @@ import (
)
func TestServer_RequestPasswordReset(t *testing.T) {
t.Parallel()
userID := Instance.CreateHumanUser(CTX).GetUserId()
tests := []struct {
@ -109,8 +107,6 @@ func TestServer_RequestPasswordReset(t *testing.T) {
}
func TestServer_SetPassword(t *testing.T) {
t.Parallel()
type args struct {
ctx context.Context
req *user.SetPasswordRequest

View File

@ -18,8 +18,6 @@ import (
)
func TestServer_SetPhone(t *testing.T) {
t.Parallel()
userID := Instance.CreateHumanUser(CTX).GetUserId()
tests := []struct {
@ -126,8 +124,6 @@ func TestServer_SetPhone(t *testing.T) {
}
func TestServer_ResendPhoneCode(t *testing.T) {
t.Parallel()
userID := Instance.CreateHumanUser(CTX).GetUserId()
verifiedUserID := Instance.CreateHumanUserVerified(CTX, Instance.DefaultOrg.Id, gofakeit.Email()).GetUserId()
@ -204,8 +200,6 @@ func TestServer_ResendPhoneCode(t *testing.T) {
}
func TestServer_VerifyPhone(t *testing.T) {
t.Parallel()
userResp := Instance.CreateHumanUser(CTX)
tests := []struct {
name string
@ -258,8 +252,6 @@ func TestServer_VerifyPhone(t *testing.T) {
}
func TestServer_RemovePhone(t *testing.T) {
t.Parallel()
userResp := Instance.CreateHumanUser(CTX)
failResp := Instance.CreateHumanUserNoPhone(CTX)
otherUser := Instance.CreateHumanUser(CTX).GetUserId()

View File

@ -29,8 +29,6 @@ func detailsV2ToV2beta(obj *object.Details) *object_v2beta.Details {
}
func TestServer_GetUserByID(t *testing.T) {
t.Parallel()
orgResp := Instance.CreateOrganization(IamCTX, fmt.Sprintf("GetUserByIDOrg-%s", gofakeit.AppName()), gofakeit.Email())
type args struct {
ctx context.Context
@ -197,8 +195,6 @@ func TestServer_GetUserByID(t *testing.T) {
}
func TestServer_GetUserByID_Permission(t *testing.T) {
t.Parallel()
timeNow := time.Now().UTC()
newOrgOwnerEmail := gofakeit.Email()
newOrg := Instance.CreateOrganization(IamCTX, fmt.Sprintf("GetHuman-%s", gofakeit.AppName()), newOrgOwnerEmail)
@ -347,8 +343,6 @@ type userAttr struct {
}
func TestServer_ListUsers(t *testing.T) {
t.Parallel()
orgResp := Instance.CreateOrganization(IamCTX, fmt.Sprintf("ListUsersOrg-%s", gofakeit.AppName()), gofakeit.Email())
userResp := Instance.CreateHumanUserVerified(IamCTX, orgResp.OrganizationId, gofakeit.Email())
type args struct {

View File

@ -18,8 +18,6 @@ import (
)
func TestServer_RegisterTOTP(t *testing.T) {
t.Parallel()
userID := Instance.CreateHumanUser(CTX).GetUserId()
Instance.RegisterUserPasskey(CTX, userID)
_, sessionToken, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userID)
@ -106,8 +104,6 @@ func TestServer_RegisterTOTP(t *testing.T) {
}
func TestServer_VerifyTOTPRegistration(t *testing.T) {
t.Parallel()
userID := Instance.CreateHumanUser(CTX).GetUserId()
Instance.RegisterUserPasskey(CTX, userID)
_, sessionToken, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userID)
@ -216,8 +212,6 @@ func TestServer_VerifyTOTPRegistration(t *testing.T) {
}
func TestServer_RemoveTOTP(t *testing.T) {
t.Parallel()
userID := Instance.CreateHumanUser(CTX).GetUserId()
Instance.RegisterUserPasskey(CTX, userID)
_, sessionToken, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userID)

View File

@ -17,8 +17,6 @@ import (
)
func TestServer_RegisterU2F(t *testing.T) {
t.Parallel()
userID := Instance.CreateHumanUser(CTX).GetUserId()
otherUser := Instance.CreateHumanUser(CTX).GetUserId()
@ -108,8 +106,6 @@ func TestServer_RegisterU2F(t *testing.T) {
}
func TestServer_VerifyU2FRegistration(t *testing.T) {
t.Parallel()
userID := Instance.CreateHumanUser(CTX).GetUserId()
Instance.RegisterUserPasskey(CTX, userID)
_, sessionToken, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userID)

View File

@ -51,8 +51,6 @@ func TestMain(m *testing.M) {
}
func TestServer_AddHumanUser(t *testing.T) {
t.Parallel()
idpResp := Instance.AddGenericOAuthProvider(IamCTX, Instance.DefaultOrg.Id)
type args struct {
ctx context.Context
@ -638,8 +636,6 @@ func TestServer_AddHumanUser(t *testing.T) {
}
func TestServer_AddHumanUser_Permission(t *testing.T) {
t.Parallel()
newOrg := Instance.CreateOrganization(IamCTX, fmt.Sprintf("AddHuman-%s", gofakeit.AppName()), gofakeit.Email())
type args struct {
ctx context.Context
@ -832,8 +828,6 @@ func TestServer_AddHumanUser_Permission(t *testing.T) {
}
func TestServer_UpdateHumanUser(t *testing.T) {
t.Parallel()
type args struct {
ctx context.Context
req *user.UpdateHumanUserRequest
@ -1195,8 +1189,6 @@ func TestServer_UpdateHumanUser(t *testing.T) {
}
func TestServer_UpdateHumanUser_Permission(t *testing.T) {
t.Parallel()
newOrg := Instance.CreateOrganization(IamCTX, fmt.Sprintf("UpdateHuman-%s", gofakeit.AppName()), gofakeit.Email())
newUserID := newOrg.CreatedAdmins[0].GetUserId()
type args struct {
@ -1279,8 +1271,6 @@ func TestServer_UpdateHumanUser_Permission(t *testing.T) {
}
func TestServer_LockUser(t *testing.T) {
t.Parallel()
type args struct {
ctx context.Context
req *user.LockUserRequest
@ -1389,8 +1379,6 @@ func TestServer_LockUser(t *testing.T) {
}
func TestServer_UnLockUser(t *testing.T) {
t.Parallel()
type args struct {
ctx context.Context
req *user.UnlockUserRequest
@ -1499,8 +1487,6 @@ func TestServer_UnLockUser(t *testing.T) {
}
func TestServer_DeactivateUser(t *testing.T) {
t.Parallel()
type args struct {
ctx context.Context
req *user.DeactivateUserRequest
@ -1609,8 +1595,6 @@ func TestServer_DeactivateUser(t *testing.T) {
}
func TestServer_ReactivateUser(t *testing.T) {
t.Parallel()
type args struct {
ctx context.Context
req *user.ReactivateUserRequest
@ -1719,8 +1703,6 @@ func TestServer_ReactivateUser(t *testing.T) {
}
func TestServer_DeleteUser(t *testing.T) {
t.Parallel()
projectResp, err := Instance.CreateProject(CTX)
require.NoError(t, err)
type args struct {
@ -1820,8 +1802,6 @@ func TestServer_DeleteUser(t *testing.T) {
}
func TestServer_AddIDPLink(t *testing.T) {
t.Parallel()
idpResp := Instance.AddGenericOAuthProvider(IamCTX, Instance.DefaultOrg.Id)
type args struct {
ctx context.Context
@ -1901,8 +1881,6 @@ func TestServer_AddIDPLink(t *testing.T) {
}
func TestServer_StartIdentityProviderIntent(t *testing.T) {
t.Parallel()
idpResp := Instance.AddGenericOAuthProvider(IamCTX, Instance.DefaultOrg.Id)
orgIdpID := Instance.AddOrgGenericOAuthProvider(CTX, Instance.DefaultOrg.Id)
orgResp := Instance.CreateOrganization(IamCTX, fmt.Sprintf("NotDefaultOrg-%s", gofakeit.AppName()), gofakeit.Email())
@ -2166,9 +2144,7 @@ func TestServer_StartIdentityProviderIntent(t *testing.T) {
/*
func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
t.Parallel()
idpID := Instance.AddGenericOAuthProvider(t, CTX)
idpID := Instance.AddGenericOAuthProvider(t, CTX)
intentID := Instance.CreateIntent(t, CTX, idpID)
successfulID, token, changeDate, sequence := Instance.CreateSuccessfulOAuthIntent(t, CTX, idpID.Id, "", "id")
successfulWithUserID, withUsertoken, withUserchangeDate, withUsersequence := Instance.CreateSuccessfulOAuthIntent(t, CTX, idpID.Id, "user", "id")
@ -2428,8 +2404,6 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
*/
func TestServer_ListAuthenticationMethodTypes(t *testing.T) {
t.Parallel()
userIDWithoutAuth := Instance.CreateHumanUser(CTX).GetUserId()
userIDWithPasskey := Instance.CreateHumanUser(CTX).GetUserId()

View File

@ -8,9 +8,11 @@ import (
"fmt"
"io"
"net/http"
"strconv"
"github.com/crewjam/saml"
"github.com/gorilla/mux"
"github.com/muhlemmer/gu"
"github.com/zitadel/logging"
http_utils "github.com/zitadel/zitadel/internal/api/http"
@ -49,6 +51,7 @@ const (
paramError = "error"
paramErrorDescription = "error_description"
varIDPID = "idpid"
paramInternalUI = "internalUI"
)
type Handler struct {
@ -187,21 +190,8 @@ func (h *Handler) handleMetadata(w http.ResponseWriter, r *http.Request) {
}
metadata := sp.ServiceProvider.Metadata()
for i, spDesc := range metadata.SPSSODescriptors {
spDesc.AssertionConsumerServices = append(
spDesc.AssertionConsumerServices,
saml.IndexedEndpoint{
Binding: saml.HTTPPostBinding,
Location: h.loginSAMLRootURL(ctx),
Index: len(spDesc.AssertionConsumerServices) + 1,
}, saml.IndexedEndpoint{
Binding: saml.HTTPArtifactBinding,
Location: h.loginSAMLRootURL(ctx),
Index: len(spDesc.AssertionConsumerServices) + 2,
},
)
metadata.SPSSODescriptors[i] = spDesc
}
internalUI, _ := strconv.ParseBool(r.URL.Query().Get(paramInternalUI))
h.assertionConsumerServices(ctx, metadata, internalUI)
buf, _ := xml.MarshalIndent(metadata, "", " ")
w.Header().Set("Content-Type", "application/samlmetadata+xml")
@ -212,6 +202,48 @@ func (h *Handler) handleMetadata(w http.ResponseWriter, r *http.Request) {
}
}
func (h *Handler) assertionConsumerServices(ctx context.Context, metadata *saml.EntityDescriptor, internalUI bool) {
if !internalUI {
for i, spDesc := range metadata.SPSSODescriptors {
spDesc.AssertionConsumerServices = append(
spDesc.AssertionConsumerServices,
saml.IndexedEndpoint{
Binding: saml.HTTPPostBinding,
Location: h.loginSAMLRootURL(ctx),
Index: len(spDesc.AssertionConsumerServices) + 1,
}, saml.IndexedEndpoint{
Binding: saml.HTTPArtifactBinding,
Location: h.loginSAMLRootURL(ctx),
Index: len(spDesc.AssertionConsumerServices) + 2,
},
)
metadata.SPSSODescriptors[i] = spDesc
}
return
}
for i, spDesc := range metadata.SPSSODescriptors {
acs := make([]saml.IndexedEndpoint, 0, len(spDesc.AssertionConsumerServices)+2)
acs = append(acs,
saml.IndexedEndpoint{
Binding: saml.HTTPPostBinding,
Location: h.loginSAMLRootURL(ctx),
Index: 0,
IsDefault: gu.Ptr(true),
},
saml.IndexedEndpoint{
Binding: saml.HTTPArtifactBinding,
Location: h.loginSAMLRootURL(ctx),
Index: 1,
})
for i := 0; i < len(spDesc.AssertionConsumerServices); i++ {
spDesc.AssertionConsumerServices[i].Index = 2 + i
acs = append(acs, spDesc.AssertionConsumerServices[i])
}
spDesc.AssertionConsumerServices = acs
metadata.SPSSODescriptors[i] = spDesc
}
}
func (h *Handler) handleACS(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
data := parseSAMLRequest(r)

View File

@ -23,6 +23,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
saml_xml "github.com/zitadel/saml/pkg/provider/xml"
"github.com/zitadel/saml/pkg/provider/xml/md"
"golang.org/x/crypto/bcrypt"
http_util "github.com/zitadel/zitadel/internal/api/http"
@ -111,13 +112,15 @@ func TestServer_SAMLMetadata(t *testing.T) {
oauthIdpResp := Instance.AddGenericOAuthProvider(CTX, Instance.DefaultOrg.Id)
type args struct {
ctx context.Context
idpID string
ctx context.Context
idpID string
internalUI bool
}
tests := []struct {
name string
args args
want int
name string
args args
want int
wantACS []md.IndexedEndpointType
}{
{
name: "saml metadata, invalid idp",
@ -142,11 +145,115 @@ func TestServer_SAMLMetadata(t *testing.T) {
idpID: samlRedirectIdpID,
},
want: http.StatusOK,
wantACS: []md.IndexedEndpointType{
{
XMLName: xml.Name{
Space: "urn:oasis:names:tc:SAML:2.0:metadata",
Local: "AssertionConsumerService",
},
Index: "1",
IsDefault: "",
Binding: saml.HTTPPostBinding,
Location: http_util.BuildOrigin(Instance.Host(), Instance.Config.Secure) + "/idps/" + samlRedirectIdpID + "/saml/acs",
ResponseLocation: "",
},
{
XMLName: xml.Name{
Space: "urn:oasis:names:tc:SAML:2.0:metadata",
Local: "AssertionConsumerService",
},
Index: "2",
IsDefault: "",
Binding: saml.HTTPArtifactBinding,
Location: http_util.BuildOrigin(Instance.Host(), Instance.Config.Secure) + "/idps/" + samlRedirectIdpID + "/saml/acs",
ResponseLocation: "",
},
{
XMLName: xml.Name{
Space: "urn:oasis:names:tc:SAML:2.0:metadata",
Local: "AssertionConsumerService",
},
Index: "3",
IsDefault: "",
Binding: saml.HTTPPostBinding,
Location: http_util.BuildOrigin(Instance.Host(), Instance.Config.Secure) + "/ui/login/login/externalidp/saml/acs",
ResponseLocation: "",
},
{
XMLName: xml.Name{
Space: "urn:oasis:names:tc:SAML:2.0:metadata",
Local: "AssertionConsumerService",
},
Index: "4",
IsDefault: "",
Binding: saml.HTTPArtifactBinding,
Location: http_util.BuildOrigin(Instance.Host(), Instance.Config.Secure) + "/ui/login/login/externalidp/saml/acs",
ResponseLocation: "",
},
},
},
{
name: "saml metadata, ok (internalUI)",
args: args{
ctx: CTX,
idpID: samlRedirectIdpID,
internalUI: true,
},
want: http.StatusOK,
wantACS: []md.IndexedEndpointType{
{
XMLName: xml.Name{
Space: "urn:oasis:names:tc:SAML:2.0:metadata",
Local: "AssertionConsumerService",
},
Index: "0",
IsDefault: "true",
Binding: saml.HTTPPostBinding,
Location: http_util.BuildOrigin(Instance.Host(), Instance.Config.Secure) + "/ui/login/login/externalidp/saml/acs",
ResponseLocation: "",
},
{
XMLName: xml.Name{
Space: "urn:oasis:names:tc:SAML:2.0:metadata",
Local: "AssertionConsumerService",
},
Index: "1",
IsDefault: "",
Binding: saml.HTTPArtifactBinding,
Location: http_util.BuildOrigin(Instance.Host(), Instance.Config.Secure) + "/ui/login/login/externalidp/saml/acs",
ResponseLocation: "",
},
{
XMLName: xml.Name{
Space: "urn:oasis:names:tc:SAML:2.0:metadata",
Local: "AssertionConsumerService",
},
Index: "2",
IsDefault: "",
Binding: saml.HTTPPostBinding,
Location: http_util.BuildOrigin(Instance.Host(), Instance.Config.Secure) + "/idps/" + samlRedirectIdpID + "/saml/acs",
ResponseLocation: "",
},
{
XMLName: xml.Name{
Space: "urn:oasis:names:tc:SAML:2.0:metadata",
Local: "AssertionConsumerService",
},
Index: "3",
IsDefault: "",
Binding: saml.HTTPArtifactBinding,
Location: http_util.BuildOrigin(Instance.Host(), Instance.Config.Secure) + "/idps/" + samlRedirectIdpID + "/saml/acs",
ResponseLocation: "",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
metadataURL := http_util.BuildOrigin(Instance.Host(), Instance.Config.Secure) + "/idps/" + tt.args.idpID + "/saml/metadata"
if tt.args.internalUI {
metadataURL = metadataURL + "?internalUI=true"
}
resp, err := http.Get(metadataURL)
assert.NoError(t, err)
assert.Equal(t, tt.want, resp.StatusCode)
@ -155,10 +262,11 @@ func TestServer_SAMLMetadata(t *testing.T) {
defer resp.Body.Close()
assert.NoError(t, err)
_, err = saml_xml.ParseMetadataXmlIntoStruct(b)
metadata, err := saml_xml.ParseMetadataXmlIntoStruct(b)
assert.NoError(t, err)
}
assert.Equal(t, metadata.SPSSODescriptor.AssertionConsumerService, tt.wantACS)
}
})
}
}

View File

@ -600,6 +600,7 @@ func (s *Server) authResponseToken(authReq *AuthRequest, authorizer op.Authorize
nil,
slices.Contains(scope, oidc.ScopeOfflineAccess),
authReq.SessionID,
authReq.oidc().ResponseType,
)
if err != nil {
op.AuthRequestError(w, r, authReq, err, authorizer)

View File

@ -28,8 +28,6 @@ var (
)
func TestOPStorage_CreateAuthRequest(t *testing.T) {
t.Parallel()
clientID, _ := createClient(t, Instance)
id := createAuthRequest(t, Instance, clientID, redirectURI)
@ -37,8 +35,6 @@ func TestOPStorage_CreateAuthRequest(t *testing.T) {
}
func TestOPStorage_CreateAccessToken_code(t *testing.T) {
t.Parallel()
clientID, _ := createClient(t, Instance)
authRequestID := createAuthRequest(t, Instance, clientID, redirectURI)
sessionID, sessionToken, startTime, changeTime := Instance.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
@ -78,8 +74,6 @@ func TestOPStorage_CreateAccessToken_code(t *testing.T) {
}
func TestOPStorage_CreateAccessToken_implicit(t *testing.T) {
t.Parallel()
clientID := createImplicitClient(t)
authRequestID := createAuthRequestImplicit(t, clientID, redirectURIImplicit)
sessionID, sessionToken, startTime, changeTime := Instance.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
@ -130,8 +124,6 @@ func TestOPStorage_CreateAccessToken_implicit(t *testing.T) {
}
func TestOPStorage_CreateAccessAndRefreshTokens_code(t *testing.T) {
t.Parallel()
clientID, _ := createClient(t, Instance)
authRequestID := createAuthRequest(t, Instance, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess)
sessionID, sessionToken, startTime, changeTime := Instance.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
@ -155,8 +147,6 @@ func TestOPStorage_CreateAccessAndRefreshTokens_code(t *testing.T) {
}
func TestOPStorage_CreateAccessAndRefreshTokens_refresh(t *testing.T) {
t.Parallel()
clientID, _ := createClient(t, Instance)
provider, err := Instance.CreateRelyingParty(CTX, clientID, redirectURI)
require.NoError(t, err)
@ -193,8 +183,6 @@ func TestOPStorage_CreateAccessAndRefreshTokens_refresh(t *testing.T) {
}
func TestOPStorage_RevokeToken_access_token(t *testing.T) {
t.Parallel()
clientID, _ := createClient(t, Instance)
provider, err := Instance.CreateRelyingParty(CTX, clientID, redirectURI)
require.NoError(t, err)
@ -238,8 +226,6 @@ func TestOPStorage_RevokeToken_access_token(t *testing.T) {
}
func TestOPStorage_RevokeToken_access_token_invalid_token_hint_type(t *testing.T) {
t.Parallel()
clientID, _ := createClient(t, Instance)
provider, err := Instance.CreateRelyingParty(CTX, clientID, redirectURI)
require.NoError(t, err)
@ -277,8 +263,6 @@ func TestOPStorage_RevokeToken_access_token_invalid_token_hint_type(t *testing.T
}
func TestOPStorage_RevokeToken_refresh_token(t *testing.T) {
t.Parallel()
clientID, _ := createClient(t, Instance)
provider, err := Instance.CreateRelyingParty(CTX, clientID, redirectURI)
require.NoError(t, err)
@ -322,8 +306,6 @@ func TestOPStorage_RevokeToken_refresh_token(t *testing.T) {
}
func TestOPStorage_RevokeToken_refresh_token_invalid_token_type_hint(t *testing.T) {
t.Parallel()
clientID, _ := createClient(t, Instance)
provider, err := Instance.CreateRelyingParty(CTX, clientID, redirectURI)
require.NoError(t, err)
@ -361,8 +343,6 @@ func TestOPStorage_RevokeToken_refresh_token_invalid_token_type_hint(t *testing.
}
func TestOPStorage_RevokeToken_invalid_client(t *testing.T) {
t.Parallel()
clientID, _ := createClient(t, Instance)
authRequestID := createAuthRequest(t, Instance, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess)
sessionID, sessionToken, startTime, changeTime := Instance.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
@ -393,8 +373,6 @@ func TestOPStorage_RevokeToken_invalid_client(t *testing.T) {
}
func TestOPStorage_TerminateSession(t *testing.T) {
t.Parallel()
clientID, _ := createClient(t, Instance)
provider, err := Instance.CreateRelyingParty(CTX, clientID, redirectURI)
require.NoError(t, err)
@ -432,8 +410,6 @@ func TestOPStorage_TerminateSession(t *testing.T) {
}
func TestOPStorage_TerminateSession_refresh_grant(t *testing.T) {
t.Parallel()
clientID, _ := createClient(t, Instance)
provider, err := Instance.CreateRelyingParty(CTX, clientID, redirectURI)
require.NoError(t, err)
@ -478,8 +454,6 @@ func TestOPStorage_TerminateSession_refresh_grant(t *testing.T) {
}
func TestOPStorage_TerminateSession_empty_id_token_hint(t *testing.T) {
t.Parallel()
clientID, _ := createClient(t, Instance)
provider, err := Instance.CreateRelyingParty(CTX, clientID, redirectURI)
require.NoError(t, err)

View File

@ -24,8 +24,6 @@ import (
)
func TestServer_Introspect(t *testing.T) {
t.Parallel()
project, err := Instance.CreateProject(CTX)
require.NoError(t, err)
app, err := Instance.CreateOIDCNativeClient(CTX, redirectURI, logoutRedirectURI, project.GetId(), false)
@ -143,8 +141,6 @@ func TestServer_Introspect(t *testing.T) {
}
func TestServer_Introspect_invalid_auth_invalid_token(t *testing.T) {
t.Parallel()
// ensure that when an invalid authentication and token is sent, the authentication error is returned
// https://github.com/zitadel/zitadel/pull/8133
resourceServer, err := Instance.CreateResourceServerClientCredentials(CTX, "xxxxx", "xxxxx")
@ -191,8 +187,6 @@ func assertIntrospection(
// TestServer_VerifyClient tests verification by running code flow tests
// with clients that have different authentication methods.
func TestServer_VerifyClient(t *testing.T) {
t.Parallel()
sessionID, sessionToken, startTime, changeTime := Instance.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
project, err := Instance.CreateProject(CTX)
require.NoError(t, err)

View File

@ -24,8 +24,6 @@ import (
)
func TestServer_Keys(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ctxLogin := instance.WithAuthorization(CTX, integration.UserTypeLogin)

View File

@ -18,8 +18,6 @@ import (
)
func TestServer_RefreshToken_Status(t *testing.T) {
t.Parallel()
clientID, _ := createClient(t, Instance)
provider, err := Instance.CreateRelyingParty(CTX, clientID, redirectURI)
require.NoError(t, err)

View File

@ -22,8 +22,6 @@ import (
)
func TestServer_ClientCredentialsExchange(t *testing.T) {
t.Parallel()
machine, name, clientID, clientSecret, err := Instance.CreateOIDCCredentialsClient(CTX)
require.NoError(t, err)

View File

@ -143,8 +143,6 @@ func refreshTokenVerifier(ctx context.Context, provider rp.RelyingParty, subject
}
func TestServer_TokenExchange(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ctx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
userResp := instance.CreateHumanUser(ctx)
@ -365,8 +363,6 @@ func TestServer_TokenExchange(t *testing.T) {
}
func TestServer_TokenExchangeImpersonation(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ctx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
userResp := instance.CreateHumanUser(ctx)
@ -581,8 +577,6 @@ func TestServer_TokenExchangeImpersonation(t *testing.T) {
// This test tries to call the zitadel API with an impersonated token,
// which should fail.
func TestImpersonation_API_Call(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ctx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)

View File

@ -21,8 +21,6 @@ import (
)
func TestServer_JWTProfile(t *testing.T) {
t.Parallel()
user, name, keyData, err := Instance.CreateOIDCJWTProfileClient(CTX)
require.NoError(t, err)

View File

@ -47,6 +47,7 @@ func (s *Server) ClientCredentialsExchange(ctx context.Context, r *op.ClientRequ
nil,
false,
"",
domain.OIDCResponseTypeUnspecified,
)
if err != nil {
return nil, err

View File

@ -87,6 +87,7 @@ func (s *Server) codeExchangeV1(ctx context.Context, client *Client, req *oidc.A
nil,
slices.Contains(scope, oidc.ScopeOfflineAccess),
authReq.SessionID,
authReq.oidc().ResponseType,
)
if err != nil {
return nil, err

View File

@ -300,6 +300,7 @@ func (s *Server) createExchangeAccessToken(
actor,
slices.Contains(scope, oidc.ScopeOfflineAccess),
"",
domain.OIDCResponseTypeUnspecified,
)
if err != nil {
return "", "", "", 0, err
@ -346,6 +347,7 @@ func (s *Server) createExchangeJWT(
actor,
slices.Contains(scope, oidc.ScopeOfflineAccess),
"",
domain.OIDCResponseTypeUnspecified,
)
accessToken, err = s.createJWT(ctx, client, session, getUserInfo, roleAssertion, getSigner)
if err != nil {

View File

@ -57,6 +57,7 @@ func (s *Server) JWTProfile(ctx context.Context, r *op.Request[oidc.JWTProfileGr
nil,
false,
"",
domain.OIDCResponseTypeUnspecified,
)
if err != nil {
return nil, err

View File

@ -69,6 +69,7 @@ func (s *Server) refreshTokenV1(ctx context.Context, client *Client, r *op.Clien
refreshToken.Actor,
true,
"",
domain.OIDCResponseTypeUnspecified,
)
if err != nil {
return nil, err

View File

@ -1,8 +1,8 @@
package login
import (
"fmt"
"net/http"
"net/url"
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
"github.com/zitadel/zitadel/internal/domain"
@ -38,13 +38,13 @@ type initPasswordData struct {
HasSymbol string
}
func InitPasswordLink(origin, userID, code, orgID, authRequestID string) string {
v := url.Values{}
v.Set(queryInitPWUserID, userID)
v.Set(queryInitPWCode, code)
v.Set(queryOrgID, orgID)
v.Set(QueryAuthRequestID, authRequestID)
return externalLink(origin) + EndpointInitPassword + "?" + v.Encode()
func InitPasswordLinkTemplate(origin, userID, orgID, authRequestID string) string {
return fmt.Sprintf("%s%s?%s=%s&%s=%s&%s=%s&%s=%s",
externalLink(origin), EndpointInitPassword,
queryInitPWUserID, userID,
queryInitPWCode, "{{.Code}}",
queryOrgID, orgID,
QueryAuthRequestID, authRequestID)
}
func (l *Login) handleInitPassword(w http.ResponseWriter, r *http.Request) {

View File

@ -1,8 +1,8 @@
package login
import (
"fmt"
"net/http"
"net/url"
"strconv"
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
@ -44,15 +44,15 @@ type initUserData struct {
HasSymbol string
}
func InitUserLink(origin, userID, loginName, code, orgID string, passwordSet bool, authRequestID string) string {
v := url.Values{}
v.Set(queryInitUserUserID, userID)
v.Set(queryInitUserLoginName, loginName)
v.Set(queryInitUserCode, code)
v.Set(queryOrgID, orgID)
v.Set(queryInitUserPassword, strconv.FormatBool(passwordSet))
v.Set(QueryAuthRequestID, authRequestID)
return externalLink(origin) + EndpointInitUser + "?" + v.Encode()
func InitUserLinkTemplate(origin, userID, orgID, authRequestID string) string {
return fmt.Sprintf("%s%s?%s=%s&%s=%s&%s=%s&%s=%s&%s=%s&%s=%s",
externalLink(origin), EndpointInitUser,
queryInitUserUserID, userID,
queryInitUserLoginName, "{{.LoginName}}",
queryInitUserCode, "{{.Code}}",
queryOrgID, orgID,
queryInitUserPassword, "{{.PasswordSet}}",
QueryAuthRequestID, authRequestID)
}
func (l *Login) handleInitUser(w http.ResponseWriter, r *http.Request) {

View File

@ -1,8 +1,8 @@
package login
import (
"fmt"
"net/http"
"net/url"
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
"github.com/zitadel/zitadel/internal/domain"
@ -40,14 +40,14 @@ type inviteUserData struct {
HasSymbol string
}
func InviteUserLink(origin, userID, loginName, code, orgID string, authRequestID string) string {
v := url.Values{}
v.Set(queryInviteUserUserID, userID)
v.Set(queryInviteUserLoginName, loginName)
v.Set(queryInviteUserCode, code)
v.Set(queryOrgID, orgID)
v.Set(QueryAuthRequestID, authRequestID)
return externalLink(origin) + EndpointInviteUser + "?" + v.Encode()
func InviteUserLinkTemplate(origin, userID, orgID string, authRequestID string) string {
return fmt.Sprintf("%s%s?%s=%s&%s=%s&%s=%s&%s=%s&%s=%s",
externalLink(origin), EndpointInviteUser,
queryInviteUserUserID, userID,
queryInviteUserLoginName, "{{.LoginName}}",
queryInviteUserCode, "{{.Code}}",
queryOrgID, orgID,
QueryAuthRequestID, authRequestID)
}
func (l *Login) handleInviteUser(w http.ResponseWriter, r *http.Request) {

View File

@ -2,8 +2,8 @@ package login
import (
"context"
"fmt"
"net/http"
"net/url"
"slices"
"github.com/zitadel/logging"
@ -43,13 +43,13 @@ type mailVerificationData struct {
HasSymbol string
}
func MailVerificationLink(origin, userID, code, orgID, authRequestID string) string {
v := url.Values{}
v.Set(queryUserID, userID)
v.Set(queryCode, code)
v.Set(queryOrgID, orgID)
v.Set(QueryAuthRequestID, authRequestID)
return externalLink(origin) + EndpointMailVerification + "?" + v.Encode()
func MailVerificationLinkTemplate(origin, userID, orgID, authRequestID string) string {
return fmt.Sprintf("%s%s?%s=%s&%s=%s&%s=%s&%s=%s",
externalLink(origin), EndpointMailVerification,
queryUserID, userID,
queryCode, "{{.Code}}",
queryOrgID, orgID,
QueryAuthRequestID, authRequestID)
}
func (l *Login) handleMailVerification(w http.ResponseWriter, r *http.Request) {

Some files were not shown because too many files have changed in this diff Show More