mirror of
https://github.com/zitadel/zitadel.git
synced 2025-03-03 23:55:25 +00:00
Merge branch 'next' into next-rc
This commit is contained in:
commit
a5f7dc8c30
10
README.md
10
README.md
@ -80,6 +80,11 @@ See all guides [here](https://zitadel.com/docs/self-hosting/deploy/overview)
|
||||
### Setup ZITADEL Cloud (SaaS)
|
||||
|
||||
If you want to experience a hands-free ZITADEL, you should use [ZITADEL Cloud](https://zitadel.com).
|
||||
Available data regions are:
|
||||
* 🇺🇸 United States
|
||||
* 🇪🇺 European Union
|
||||
* 🇦🇺 Australia
|
||||
* 🇨🇭 Switzerland
|
||||
|
||||
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).
|
||||
@ -149,11 +154,12 @@ Deployment
|
||||
- [Zero Downtime Updates](https://zitadel.com/docs/concepts/architecture/solution#zero-downtime-updates)
|
||||
- [High scalability](https://zitadel.com/docs/self-hosting/manage/production)
|
||||
|
||||
Track upcoming features on our [roadmap](https://zitadel.com/roadmap).
|
||||
Track upcoming features on our [roadmap](https://zitadel.com/roadmap) and follow our [changelog](https://zitadel.com/changelog) for recent updates.
|
||||
|
||||
## How To Contribute
|
||||
|
||||
Find details about how you can contribute in our [Contribution Guide](./CONTRIBUTING.md)
|
||||
Find details about how you can contribute in our [Contribution Guide](./CONTRIBUTING.md).
|
||||
Join our [Discord Chat](https://zitadel.com/chat) to get help.
|
||||
|
||||
## Contributors
|
||||
|
||||
|
@ -3,8 +3,6 @@ package mirror
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/v2/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/v2/projection"
|
||||
"github.com/zitadel/zitadel/internal/v2/readmodel"
|
||||
@ -32,12 +30,12 @@ func queryLastSuccessfulMigration(ctx context.Context, destinationES *eventstore
|
||||
return lastSuccess, nil
|
||||
}
|
||||
|
||||
func writeMigrationStart(ctx context.Context, sourceES *eventstore.EventStore, id string, destination string) (_ decimal.Decimal, err error) {
|
||||
func writeMigrationStart(ctx context.Context, sourceES *eventstore.EventStore, id string, destination string) (_ float64, err error) {
|
||||
var cmd *eventstore.Command
|
||||
if len(instanceIDs) > 0 {
|
||||
cmd, err = mirror_event.NewStartedInstancesCommand(destination, instanceIDs)
|
||||
if err != nil {
|
||||
return decimal.Decimal{}, err
|
||||
return 0, err
|
||||
}
|
||||
} else {
|
||||
cmd = mirror_event.NewStartedSystemCommand(destination)
|
||||
@ -60,12 +58,12 @@ func writeMigrationStart(ctx context.Context, sourceES *eventstore.EventStore, i
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return decimal.Decimal{}, err
|
||||
return 0, err
|
||||
}
|
||||
return position.Position, nil
|
||||
}
|
||||
|
||||
func writeMigrationSucceeded(ctx context.Context, destinationES *eventstore.EventStore, id, source string, position decimal.Decimal) error {
|
||||
func writeMigrationSucceeded(ctx context.Context, destinationES *eventstore.EventStore, id, source string, position float64) error {
|
||||
return destinationES.Push(
|
||||
ctx,
|
||||
eventstore.NewPushIntent(
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5/stdlib"
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/zitadel/logging"
|
||||
@ -181,7 +180,7 @@ func copyEvents(ctx context.Context, source, dest *db.DB, bulkSize uint32) {
|
||||
logging.WithFields("took", time.Since(start), "count", eventCount).Info("events migrated")
|
||||
}
|
||||
|
||||
func writeCopyEventsDone(ctx context.Context, es *eventstore.EventStore, id, source string, position decimal.Decimal, errs <-chan error) {
|
||||
func writeCopyEventsDone(ctx context.Context, es *eventstore.EventStore, id, source string, position float64, errs <-chan error) {
|
||||
joinedErrs := make([]error, 0, len(errs))
|
||||
for err := range errs {
|
||||
joinedErrs = append(joinedErrs, err)
|
||||
|
@ -24,7 +24,7 @@
|
||||
<th class="availability" mat-header-cell *matHeaderCellDef>
|
||||
<span>{{ 'SMTP.LIST.ACTIVATED' | translate }}</span>
|
||||
</th>
|
||||
<td class="pointer availability" mat-cell *matCellDef="let config">
|
||||
<td class="availability" [ngClass]="{ pointer: config.senderAddress }" mat-cell *matCellDef="let config">
|
||||
<i
|
||||
matTooltip="{{ 'SMTP.LIST.ACTIVATED' | translate }}"
|
||||
*ngIf="isActive(config.state)"
|
||||
@ -36,7 +36,7 @@
|
||||
|
||||
<ng-container matColumnDef="description">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'SETTING.SMTP.DESCRIPTION' | translate }}</th>
|
||||
<td class="pointer" mat-cell *matCellDef="let config">
|
||||
<td [ngClass]="{ pointer: config.senderAddress }" mat-cell *matCellDef="let config">
|
||||
<span>{{ config?.description }}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
@ -45,22 +45,22 @@
|
||||
<th class="availability" mat-header-cell *matHeaderCellDef>
|
||||
<span>TLS</span>
|
||||
</th>
|
||||
<td class="pointer availability" mat-cell *matCellDef="let config">
|
||||
<td class="availability" [ngClass]="{ pointer: config.senderAddress }" mat-cell *matCellDef="let config">
|
||||
<i *ngIf="config.tls" class="las la-lock"></i>
|
||||
<i *ngIf="!config.tls" class="las la-unlock"></i>
|
||||
<i *ngIf="config.senderAddress && !config.tls" class="las la-unlock"></i>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="host">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'SETTING.SMTP.HOSTANDPORT' | translate }}</th>
|
||||
<td class="pointer" mat-cell *matCellDef="let config">
|
||||
<td [ngClass]="{ pointer: config.senderAddress }" mat-cell *matCellDef="let config">
|
||||
<span>{{ config?.host }}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="senderAddress">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'SETTING.SMTP.SENDERADDRESS' | translate }}</th>
|
||||
<td class="pointer" mat-cell *matCellDef="let config">
|
||||
<td [ngClass]="{ pointer: config.senderAddress }" mat-cell *matCellDef="let config">
|
||||
<span>{{ config?.senderAddress }}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
@ -95,7 +95,7 @@
|
||||
|
||||
<button
|
||||
actions
|
||||
[disabled]="(['iam.write'] | hasRole | async) === false"
|
||||
[disabled]="(['iam.write'] | hasRole | async) === false || !config?.senderAddress"
|
||||
mat-icon-button
|
||||
color="primary"
|
||||
matTooltip="{{ 'SMTP.LIST.TEST' | translate }}"
|
||||
|
@ -205,6 +205,9 @@ export class SMTPTableComponent implements OnInit {
|
||||
}
|
||||
|
||||
public navigateToProvider(row: SMTPConfig.AsObject) {
|
||||
if (!row.senderAddress) {
|
||||
return;
|
||||
}
|
||||
this.router.navigate(this.routerLinkForRow(row));
|
||||
}
|
||||
}
|
||||
|
@ -154,10 +154,25 @@
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="state">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'PROJECT.GRANT.STATE' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let grant">
|
||||
<span
|
||||
class="state"
|
||||
[ngClass]="{
|
||||
active: grant.state === UserGrantState.USER_GRANT_STATE_ACTIVE,
|
||||
inactive: grant.state === UserGrantState.USER_GRANT_STATE_INACTIVE,
|
||||
}"
|
||||
>
|
||||
{{ 'USER.DATA.STATE' + grant.state | translate }}
|
||||
</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions" stickyEnd>
|
||||
<th mat-header-cell *matHeaderCellDef class="user-tr-actions"></th>
|
||||
<td mat-cell class="user-tr-actions" *matCellDef="let grant; let i = index">
|
||||
<cnsl-table-actions [hasActions]="true">
|
||||
<cnsl-table-actions [hasActions]="!context.includes('user')">
|
||||
<button
|
||||
actions
|
||||
matTooltip="{{ 'ACTIONS.REMOVE' | translate }}"
|
||||
|
@ -8,7 +8,13 @@ import { tap } from 'rxjs/operators';
|
||||
import { enterAnimations } from 'src/app/animations';
|
||||
import { UserGrant as AuthUserGrant } from 'src/app/proto/generated/zitadel/auth_pb';
|
||||
import { Role } from 'src/app/proto/generated/zitadel/project_pb';
|
||||
import { Type, UserGrant as MgmtUserGrant, UserGrantQuery, UserGrant } from 'src/app/proto/generated/zitadel/user_pb';
|
||||
import {
|
||||
Type,
|
||||
UserGrant as MgmtUserGrant,
|
||||
UserGrant,
|
||||
UserGrantQuery,
|
||||
UserGrantState,
|
||||
} from 'src/app/proto/generated/zitadel/user_pb';
|
||||
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
@ -66,6 +72,7 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
|
||||
public UserGrantContext: any = UserGrantContext;
|
||||
public Type: any = Type;
|
||||
public ActionKeysType: any = ActionKeysType;
|
||||
public UserGrantState: any = UserGrantState;
|
||||
@Input() public type: Type | undefined = undefined;
|
||||
|
||||
public filterOpen: boolean = false;
|
||||
@ -86,6 +93,7 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
|
||||
'type',
|
||||
'creationDate',
|
||||
'changeDate',
|
||||
'state',
|
||||
'roleNamesList',
|
||||
'actions',
|
||||
];
|
||||
|
@ -17,6 +17,7 @@
|
||||
'type',
|
||||
'creationDate',
|
||||
'changeDate',
|
||||
'state',
|
||||
'roleNamesList',
|
||||
'actions',
|
||||
]"
|
||||
|
@ -42,7 +42,16 @@
|
||||
[context]="UserGrantContext.GRANTED_PROJECT"
|
||||
[projectId]="projectId"
|
||||
[grantId]="grantId"
|
||||
[displayedColumns]="['select', 'user', 'projectId', 'creationDate', 'changeDate', 'roleNamesList', 'actions']"
|
||||
[displayedColumns]="[
|
||||
'select',
|
||||
'user',
|
||||
'projectId',
|
||||
'creationDate',
|
||||
'changeDate',
|
||||
'state',
|
||||
'roleNamesList',
|
||||
'actions',
|
||||
]"
|
||||
[disableWrite]="(['user.grant.write$', 'user.grant.write:' + grantId] | hasRole | async) === false"
|
||||
[disableDelete]="(['user.grant.delete$', 'user.grant.delete:' + grantId] | hasRole | async) === false"
|
||||
[refreshOnPreviousRoutes]="['/grant-create/project/{{projectId}}/grant/{{grantId}}']"
|
||||
|
@ -187,7 +187,7 @@
|
||||
<cnsl-user-grants
|
||||
[context]="UserGrantContext.OWNED_PROJECT"
|
||||
[projectId]="projectId"
|
||||
[displayedColumns]="['select', 'user', 'creationDate', 'changeDate', 'roleNamesList', 'actions']"
|
||||
[displayedColumns]="['select', 'user', 'creationDate', 'changeDate', 'state', 'roleNamesList', 'actions']"
|
||||
[refreshOnPreviousRoutes]="['/grant-create/project/' + projectId]"
|
||||
[disableWrite]="(['user.grant.write$', 'user.grant.write:' + projectId] | hasRole | async) === false"
|
||||
[disableDelete]="(['user.grant.delete$', 'user.grant.delete:' + projectId] | hasRole | async) === false"
|
||||
|
@ -136,7 +136,16 @@
|
||||
<cnsl-user-grants
|
||||
[userId]="user.id"
|
||||
[context]="USERGRANTCONTEXT"
|
||||
[displayedColumns]="['org', 'projectId', 'type', 'creationDate', 'changeDate', 'roleNamesList', 'actions']"
|
||||
[displayedColumns]="[
|
||||
'org',
|
||||
'projectId',
|
||||
'type',
|
||||
'creationDate',
|
||||
'changeDate',
|
||||
'state',
|
||||
'roleNamesList',
|
||||
'actions',
|
||||
]"
|
||||
[disableWrite]="(['user.grant.write$'] | hasRole | async) === false"
|
||||
[disableDelete]="(['user.grant.delete$'] | hasRole | async) === false"
|
||||
>
|
||||
|
@ -222,7 +222,7 @@
|
||||
<cnsl-user-grants
|
||||
[userId]="user.id"
|
||||
[context]="USERGRANTCONTEXT"
|
||||
[displayedColumns]="['select', 'projectId', 'creationDate', 'changeDate', 'roleNamesList', 'actions']"
|
||||
[displayedColumns]="['select', 'projectId', 'creationDate', 'changeDate', 'state', 'roleNamesList', 'actions']"
|
||||
[disableWrite]="(['user.grant.write$'] | hasRole | async) === false"
|
||||
[disableDelete]="(['user.grant.delete$'] | hasRole | async) === false"
|
||||
>
|
||||
|
@ -12,8 +12,6 @@ The typescript repository contains all TypeScript and JavaScript packages and ap
|
||||
- `@zitadel/proto`: Typescript implementation of Protocol Buffers, suitable for web browsers and Node.js.
|
||||
- `@zitadel/client`: Core components for establishing a client connection
|
||||
- `@zitadel/node`: Core components for establishing a server connection
|
||||
- `@zitadel/react`: Shared React Utilities and components built with Tailwind CSS
|
||||
- `@zitadel/next`: Shared Next.js Utilities
|
||||
- `@zitadel/tsconfig`: shared `tsconfig.json`s used throughout the monorepo
|
||||
- `eslint-config-zitadel`: ESLint preset
|
||||
|
||||
@ -47,10 +45,13 @@ It does so by redirecting to `/login`.
|
||||
The login is then able to load an [AuthRequest](/docs/apis/resources/oidc_service_v2/oidc-service-get-auth-request#get-oidc-auth-request-details).
|
||||
|
||||
The Auth Request defines how users proceed to authenticate. If no special prompts or scopes are set, the login brings up the `/loginname` page.
|
||||
The /loginname page allows to enter loginname or email of a user. User discovery is implemented at /api/loginname and if the user is found, they will be redirected to the available authentication method page.
|
||||
Right after the user is found, a session is created and set as cookie.
|
||||
This cookie is then hydrated with more information once the users continues.
|
||||
The /loginname page allows to enter loginname or email of a user which is then used to search for a user.
|
||||
If the user is found, a session is created and set as cookie, then the user is redirected to the available authentication method page.
|
||||
While the users continues and provides more information, the cookie is hydrated with this information until a final state is reached.
|
||||
|
||||
The communication from the browser to the server is done by [NextJS Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations).
|
||||
The OIDC Auth request is always passed in the url to have a context to the ongoing authentication flow.
|
||||
|
||||
If enough user information is retrieved and the user is authenticated according to the policies, the flow is finalized by [requesting a the callback url](/docs/apis/resources/oidc_service_v2/oidc-service-create-callback) for the auth request and the user is redirected back to the application.
|
||||
The application can then request a token calling the /token endpoint of the login which is proxied to the ZITADEL API.
|
||||
|
||||
@ -91,9 +92,9 @@ The application can then request a token calling the /token endpoint of the logi
|
||||
- [x] Passkey
|
||||
- [x] IDPs
|
||||
- [x] Google
|
||||
- [ ] GitHub
|
||||
- [x] GitHub
|
||||
- [x] GitLab
|
||||
- [ ] Azure
|
||||
- [x] Azure
|
||||
- [ ] Apple
|
||||
- Register
|
||||
- [x] Email Password
|
||||
@ -113,7 +114,7 @@ Authenticated users are directly able to self service their account.
|
||||
- [x] OTP via email
|
||||
- [x] OTP via SMS
|
||||
- [x] Setup Multifactor Passkey (U2F)
|
||||
- [ ] Validate Account (email verification)
|
||||
- [x] Validate Account (email verification)
|
||||
|
||||
### Setup
|
||||
|
||||
@ -122,10 +123,18 @@ In order to run the new login app, make sure to follow the instructions in the [
|
||||
#### How to setup domains for OIDC
|
||||
|
||||
When setting up the new login app for OIDC, the domain must be registered on your instance and use https.
|
||||
If you are using a self hosted instance, [install](/docs/apis/resources/system/system-service-add-domain) your domain on your instance using the system service.
|
||||
To register your login domain on your instance, [add](/docs/apis/resources/admin/admin-service-add-instance-trusted-domain) your domain on your instances trusted domains.
|
||||
|
||||
If you want to use the login with your cloud instance, you can purchase your domain on zitadel.com, install it on your domain following [our guide](/docs/guides/manage/cloud/instances#add-custom-domain).
|
||||
After your domain has been verified, you can reconfigure your DNS settings in order to deploy the login on your own.
|
||||
#### OIDC Proxy
|
||||
|
||||
When setting up the new login app for OIDC, ensure it meets the following requirements:
|
||||
|
||||
- The OIDC Proxy is deployed and running on HTTPS
|
||||
- The OIDC Proxy sets `x-zitadel-login-client` which is the user ID of the service account
|
||||
- The OIDC Proxy sets `x-zitadel-public-host` which is the host, your login is deployed to `ex. login.example.com`.
|
||||
- The OIDC Proxy sets `x-zitadel-instance-host` which is the host of your instance `ex. test-hdujwl.zitadel.cloud`.
|
||||
|
||||
You can review an example implementation of a middlware [here](https://github.com/zitadel/typescript/blob/main/apps/login/src/middleware.ts).
|
||||
|
||||
#### Deploy to Vercel
|
||||
|
||||
@ -135,5 +144,6 @@ To deploy your own version on Vercel, navigate to your instance and create a ser
|
||||
Copy its id from the overview and set it as `ZITADEL_SERVICE_USER_ID`.
|
||||
Then create a personal access token (PAT), copy and set it as `ZITADEL_SERVICE_USER_TOKEN`, then navigate to Default settings and make sure it gets `IAM_OWNER` permissions.
|
||||
Finally set your instance url as `ZITADEL_API_URL`. Make sure to set it without trailing slash.
|
||||
Also ensure your login domain is registered on your instance by adding it as a [trusted domain](/docs/apis/resources/admin/admin-service-add-instance-trusted-domain).
|
||||
|
||||

|
||||
|
74
docs/docs/guides/manage/customize/notifications.md
Normal file
74
docs/docs/guides/manage/customize/notifications.md
Normal file
@ -0,0 +1,74 @@
|
||||
---
|
||||
title: SMS, SMTP and HTTP Provider for Notifications
|
||||
---
|
||||
|
||||
All Notifications send as SMS and Email are customizable as that you can define your own providers,
|
||||
which then send the notifications out. These providers can also be defined as an HTTP type,
|
||||
and the text and content, which is used to send the SMS's and Emails will get send to a Webhook as JSON.
|
||||
|
||||
With this everything can be customized or even custom logic can be implemented to use a not yet supported provider by ZITADEL.
|
||||
|
||||
## How it works
|
||||
|
||||
There is a default provider configured in ZITADEL Cloud, both for SMS's and Emails, but this default providers can be changed through the respective API's.
|
||||
|
||||
This API's are provided on an instance level:
|
||||
- [SMS Providers](/apis/resources/admin/sms-provider)
|
||||
- [Email Providers](/apis/resources/admin/email-provider)
|
||||
|
||||
To use a non-default provider just add, and then activate. There can only be 1 provider be activated at the same time.
|
||||
|
||||
## Resulting messages
|
||||
|
||||
In case of the Twilio and SMTP providers, the messages will be sent as before, in case of the HTTP providers the content of the messages is the same but as a HTTP call.
|
||||
|
||||
Here an example of the body of an Email sent via HTTP provider:
|
||||
```json
|
||||
{
|
||||
"contextInfo": {
|
||||
"eventType": "user.human.initialization.code.added",
|
||||
"provider": {
|
||||
"id": "285181292935381355",
|
||||
"description": "test"
|
||||
},
|
||||
"recipientEmailAddress": "example@zitadel.com"
|
||||
},
|
||||
"templateData": {
|
||||
"title": "Zitadel - Initialize User",
|
||||
"preHeader": "Initialize User",
|
||||
"subject": "Initialize User",
|
||||
"greeting": "Hello GivenName FamilyName,",
|
||||
"text": "This user was created in Zitadel. Use the username Username to login. Please click the button below to finish the initialization process. (Code 0M53RF) If you didn't ask for this mail, please ignore it.",
|
||||
"url": "http://example.zitadel.com/ui/login/user/init?authRequestID=\u0026code=0M53RF\u0026loginname=Username\u0026orgID=275353657317327214\u0026passwordset=false\u0026userID=285181014567813483",
|
||||
"buttonText": "Finish initialization",
|
||||
"primaryColor": "#5469d4",
|
||||
"backgroundColor": "#fafafa",
|
||||
"fontColor": "#000000",
|
||||
"fontFamily": "-apple-system, BlinkMacSystemFont, Segoe UI, Lato, Arial, Helvetica, sans-serif",
|
||||
"footerText": "InitCode.Footer"
|
||||
},
|
||||
"args": {
|
||||
"changeDate": "2024-09-16T10:58:50.73237+02:00",
|
||||
"code": "0M53RF",
|
||||
"creationDate": "2024-09-16T10:58:50.73237+02:00",
|
||||
"displayName": "GivenName FamilyName",
|
||||
"firstName": "GivenName",
|
||||
"lastEmail": "example@zitadel.com",
|
||||
"lastName": "FamilyName",
|
||||
"lastPhone": "+41791234567",
|
||||
"loginNames": [
|
||||
"Username"
|
||||
],
|
||||
"nickName": "",
|
||||
"preferredLoginName": "Username",
|
||||
"userName": "Username",
|
||||
"verifiedEmail": "example@zitadel.com",
|
||||
"verifiedPhone": ""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
There are 3 elements to this message:
|
||||
- contextInfo, with information on why this message is sent like the Event, which Email or SMS provider is used and which recipient should receive this message
|
||||
- templateData, with all texts and format information which can be used with a template to produce the desired message
|
||||
- args, with the information provided to the user which can be used in the message to customize
|
@ -0,0 +1,37 @@
|
||||
If you operate Zitadel behind a Reverse Proxy or Ingress inside a Kubernetes cluster,
|
||||
you may encounter an Error like `upstream sent too big header while reading response header from upstream`
|
||||
in your NGINX Logs and receive a 403 Error when accessing NGINX.
|
||||
|
||||
you can solve it by increasing the grpc buffer size in your nginx config:
|
||||
|
||||
### Ingress NGINX
|
||||
```yaml
|
||||
ingress:
|
||||
enabled: true
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/modsecurity-snippet: |
|
||||
SecRuleRemoveById 949110
|
||||
nginx.ingress.kubernetes.io/backend-protocol: "GRPC"
|
||||
nginx.ingress.kubernetes.io/configuration-snippet: |
|
||||
grpc_set_header Host $host;
|
||||
more_clear_input_headers "Host" "X-Forwarded-Host";
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Forwarded-Host $http_x_forwarded_host;
|
||||
# highlight-next-line
|
||||
nginx.ingress.kubernetes.io/server-snippet: "grpc_buffer_size 8k;"
|
||||
```
|
||||
### NGINX Config
|
||||
```nginx
|
||||
http {
|
||||
server {
|
||||
listen 80;
|
||||
http2 on;
|
||||
location / {
|
||||
grpc_pass grpc://zitadel-disabled-tls:8080;
|
||||
grpc_set_header Host $host:$server_port;
|
||||
# highlight-next-line
|
||||
grpc_buffer_size 8k;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
@ -3,8 +3,14 @@ title: Troubleshoot ZITADEL
|
||||
---
|
||||
|
||||
import InstanceNotFound from '/docs/self-hosting/deploy/troubleshooting/_instance_not_found.mdx';
|
||||
import UpstreamHeader from '/docs/self-hosting/deploy/troubleshooting/_upstream_header.mdx'
|
||||
|
||||
## Instance not found
|
||||
|
||||
|
||||
<InstanceNotFound/>
|
||||
<InstanceNotFound/>
|
||||
|
||||
|
||||
## upstream sent too big header while reading response header from upstream
|
||||
|
||||
<UpstreamHeader/>
|
@ -158,6 +158,7 @@ module.exports = {
|
||||
"guides/manage/customize/texts",
|
||||
"guides/manage/customize/behavior",
|
||||
"guides/manage/customize/restrictions",
|
||||
"guides/manage/customize/notifications",
|
||||
],
|
||||
},
|
||||
{
|
||||
|
BIN
docs/static/img/typescript-login-architecture.png
vendored
BIN
docs/static/img/typescript-login-architecture.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 69 KiB |
2
go.mod
2
go.mod
@ -38,7 +38,6 @@ require (
|
||||
github.com/h2non/gock v1.2.0
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||
github.com/improbable-eng/grpc-web v0.15.0
|
||||
github.com/jackc/pgx-shopspring-decimal v0.0.0-20220624020537-1d36b5a1853e
|
||||
github.com/jackc/pgx/v5 v5.6.0
|
||||
github.com/jarcoal/jpath v0.0.0-20140328210829-f76b8b2dbf52
|
||||
github.com/jinzhu/gorm v1.9.16
|
||||
@ -55,7 +54,6 @@ require (
|
||||
github.com/rakyll/statik v0.1.7
|
||||
github.com/rs/cors v1.11.0
|
||||
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
|
||||
github.com/shopspring/decimal v1.4.0
|
||||
github.com/sony/sonyflake v1.2.0
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/spf13/viper v1.19.0
|
||||
|
4
go.sum
4
go.sum
@ -405,8 +405,6 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA=
|
||||
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx-shopspring-decimal v0.0.0-20220624020537-1d36b5a1853e h1:i3gQ/Zo7sk4LUVbsAjTNeC4gIjoPNIZVzs4EXstssV4=
|
||||
github.com/jackc/pgx-shopspring-decimal v0.0.0-20220624020537-1d36b5a1853e/go.mod h1:zUHglCZ4mpDUPgIwqEKoba6+tcUQzRdb1+DPTuYe9pI=
|
||||
github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
|
||||
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
|
||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||
@ -651,8 +649,6 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0
|
||||
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4=
|
||||
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
|
@ -76,7 +76,7 @@ func (s *SystemTokenVerifierFromConfig) VerifySystemToken(ctx context.Context, t
|
||||
|
||||
type systemJWTStorage struct {
|
||||
keys map[string]*SystemAPIUser
|
||||
mutex sync.Mutex
|
||||
mutex sync.RWMutex
|
||||
cachedKeys map[string]*rsa.PublicKey
|
||||
}
|
||||
|
||||
@ -98,7 +98,9 @@ func (s *SystemAPIUser) readKey() (*rsa.PublicKey, error) {
|
||||
}
|
||||
|
||||
func (s *systemJWTStorage) GetKeyByIDAndClientID(_ context.Context, _, userID string) (*jose.JSONWebKey, error) {
|
||||
s.mutex.RLock()
|
||||
cachedKey, ok := s.cachedKeys[userID]
|
||||
s.mutex.RUnlock()
|
||||
if ok {
|
||||
return &jose.JSONWebKey{KeyID: userID, Key: cachedKey}, nil
|
||||
}
|
||||
|
@ -20,17 +20,20 @@ func listSMTPConfigsToModel(req *admin_pb.ListSMTPConfigsRequest) (*query.SMTPCo
|
||||
}
|
||||
|
||||
func SMTPConfigToProviderPb(config *query.SMTPConfig) *settings_pb.SMTPConfig {
|
||||
return &settings_pb.SMTPConfig{
|
||||
Details: object.ToViewDetailsPb(config.Sequence, config.CreationDate, config.ChangeDate, config.ResourceOwner),
|
||||
Id: config.ID,
|
||||
Description: config.Description,
|
||||
Tls: config.SMTPConfig.TLS,
|
||||
Host: config.SMTPConfig.Host,
|
||||
User: config.SMTPConfig.User,
|
||||
State: SMTPConfigStateToPb(config.State),
|
||||
SenderAddress: config.SMTPConfig.SenderAddress,
|
||||
SenderName: config.SMTPConfig.SenderName,
|
||||
ret := &settings_pb.SMTPConfig{
|
||||
Details: object.ToViewDetailsPb(config.Sequence, config.CreationDate, config.ChangeDate, config.ResourceOwner),
|
||||
Id: config.ID,
|
||||
Description: config.Description,
|
||||
State: SMTPConfigStateToPb(config.State),
|
||||
}
|
||||
if config.SMTPConfig != nil {
|
||||
ret.Tls = config.SMTPConfig.TLS
|
||||
ret.Host = config.SMTPConfig.Host
|
||||
ret.User = config.SMTPConfig.User
|
||||
ret.SenderAddress = config.SMTPConfig.SenderAddress
|
||||
ret.SenderName = config.SMTPConfig.SenderName
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func SMTPConfigsToPb(configs []*query.SMTPConfig) []*settings_pb.SMTPConfig {
|
||||
|
@ -55,5 +55,6 @@ func UserGrantToPb(grant *query.UserGrant) *auth_pb.UserGrant {
|
||||
ProjectGrantId: grant.GrantID,
|
||||
RoleKeys: grant.Roles,
|
||||
UserType: user.TypeToPb(grant.UserType),
|
||||
State: user.UserGrantStateToPb(grant.State),
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ func UserGrantToPb(assetPrefix string, grant *query.UserGrant) *user_pb.UserGran
|
||||
return &user_pb.UserGrant{
|
||||
Id: grant.ID,
|
||||
UserId: grant.UserID,
|
||||
State: user_pb.UserGrantState_USER_GRANT_STATE_ACTIVE,
|
||||
State: UserGrantStateToPb(grant.State),
|
||||
RoleKeys: grant.Roles,
|
||||
ProjectId: grant.ProjectID,
|
||||
OrgId: grant.ResourceOwner,
|
||||
@ -51,6 +51,21 @@ func UserGrantToPb(assetPrefix string, grant *query.UserGrant) *user_pb.UserGran
|
||||
}
|
||||
}
|
||||
|
||||
func UserGrantStateToPb(state domain.UserGrantState) user_pb.UserGrantState {
|
||||
switch state {
|
||||
case domain.UserGrantStateActive:
|
||||
return user_pb.UserGrantState_USER_GRANT_STATE_ACTIVE
|
||||
case domain.UserGrantStateInactive:
|
||||
return user_pb.UserGrantState_USER_GRANT_STATE_INACTIVE
|
||||
case domain.UserGrantStateRemoved,
|
||||
domain.UserGrantStateUnspecified:
|
||||
// these states should never occur here and are mainly listed for linting purposes
|
||||
fallthrough
|
||||
default:
|
||||
return user_pb.UserGrantState_USER_GRANT_STATE_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
func UserGrantQueriesToQuery(ctx context.Context, queries []*user_pb.UserGrantQuery) (q []query.SearchQuery, err error) {
|
||||
q = make([]query.SearchQuery, len(queries))
|
||||
for i, query := range queries {
|
||||
|
@ -50,13 +50,10 @@ func (o *OPStorage) GetClientByClientID(ctx context.Context, id string) (_ op.Cl
|
||||
err = oidcError(err)
|
||||
span.EndWithError(err)
|
||||
}()
|
||||
client, err := o.query.GetOIDCClientByID(ctx, id, false)
|
||||
client, err := o.query.ActiveOIDCClientByID(ctx, id, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if client.State != domain.AppStateActive {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "OIDC-sdaGg", "client is not active")
|
||||
}
|
||||
return ClientFromBusiness(client, o.defaultLoginURL, o.defaultLoginURLV2), nil
|
||||
}
|
||||
|
||||
@ -333,6 +330,9 @@ func (o *OPStorage) setUserinfo(ctx context.Context, userInfo *oidc.UserInfo, us
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if user.State != domain.UserStateActive {
|
||||
return zerrors.ThrowUnauthenticated(nil, "OIDC-S3tha", "Errors.Users.NotActive")
|
||||
}
|
||||
var allRoles bool
|
||||
roles := make([]string, 0)
|
||||
for _, scope := range scopes {
|
||||
@ -802,19 +802,24 @@ func (o *OPStorage) assertRoles(ctx context.Context, userID, applicationID strin
|
||||
if projectID != "" {
|
||||
roleAudience = append(roleAudience, projectID)
|
||||
}
|
||||
queries := make([]query.SearchQuery, 0, 2)
|
||||
projectQuery, err := query.NewUserGrantProjectIDsSearchQuery(roleAudience)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
queries = append(queries, projectQuery)
|
||||
userIDQuery, err := query.NewUserGrantUserIDSearchQuery(userID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
queries = append(queries, userIDQuery)
|
||||
activeQuery, err := query.NewUserGrantStateQuery(domain.UserGrantStateActive)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
grants, err := o.query.UserGrants(ctx, &query.UserGrantsQueries{
|
||||
Queries: queries,
|
||||
Queries: []query.SearchQuery{
|
||||
projectQuery,
|
||||
userIDQuery,
|
||||
activeQuery,
|
||||
},
|
||||
}, true)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@ -979,16 +984,13 @@ func (s *Server) VerifyClient(ctx context.Context, r *op.Request[op.ClientCreden
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err := s.query.GetOIDCClientByID(ctx, clientID, assertion)
|
||||
client, err := s.query.ActiveOIDCClientByID(ctx, clientID, assertion)
|
||||
if zerrors.IsNotFound(err) {
|
||||
return nil, oidc.ErrInvalidClient().WithParent(err).WithReturnParentToClient(authz.GetFeatures(ctx).DebugOIDCParentError).WithDescription("client not found")
|
||||
return nil, oidc.ErrInvalidClient().WithParent(err).WithReturnParentToClient(authz.GetFeatures(ctx).DebugOIDCParentError).WithDescription("no active client not found")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err // defaults to server error
|
||||
}
|
||||
if client.State != domain.AppStateActive {
|
||||
return nil, oidc.ErrInvalidClient().WithDescription("client is not active")
|
||||
}
|
||||
if client.Settings == nil {
|
||||
client.Settings = &query.OIDCSettings{
|
||||
AccessTokenLifetime: s.defaultAccessTokenLifetime,
|
||||
|
@ -196,9 +196,13 @@ func TestServer_VerifyClient(t *testing.T) {
|
||||
sessionID, sessionToken, startTime, changeTime := Instance.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
|
||||
project, err := Instance.CreateProject(CTX)
|
||||
require.NoError(t, err)
|
||||
projectInactive, err := Instance.CreateProject(CTX)
|
||||
require.NoError(t, err)
|
||||
|
||||
inactiveClient, err := Instance.CreateOIDCInactivateClient(CTX, redirectURI, logoutRedirectURI, project.GetId())
|
||||
require.NoError(t, err)
|
||||
inactiveProjectClient, err := Instance.CreateOIDCInactivateProjectClient(CTX, redirectURI, logoutRedirectURI, projectInactive.GetId())
|
||||
require.NoError(t, err)
|
||||
nativeClient, err := Instance.CreateOIDCNativeClient(CTX, redirectURI, logoutRedirectURI, project.GetId(), false)
|
||||
require.NoError(t, err)
|
||||
basicWebClient, err := Instance.CreateOIDCWebClientBasic(CTX, redirectURI, logoutRedirectURI, project.GetId())
|
||||
@ -240,6 +244,14 @@ func TestServer_VerifyClient(t *testing.T) {
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "client inactive (project) error",
|
||||
client: clientDetails{
|
||||
authReqClientID: nativeClient.GetClientId(),
|
||||
clientID: inactiveProjectClient.GetClientId(),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "native client success",
|
||||
client: clientDetails{
|
||||
|
@ -24,6 +24,9 @@ func TestServer_ClientCredentialsExchange(t *testing.T) {
|
||||
machine, name, clientID, clientSecret, err := Instance.CreateOIDCCredentialsClient(CTX)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, clientIDInactive, clientSecretInactive, err := Instance.CreateOIDCCredentialsClientInactive(CTX)
|
||||
require.NoError(t, err)
|
||||
|
||||
type claims struct {
|
||||
name string
|
||||
username string
|
||||
@ -71,6 +74,13 @@ func TestServer_ClientCredentialsExchange(t *testing.T) {
|
||||
scope: []string{oidc.ScopeOpenID},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "inactive machine user error",
|
||||
clientID: clientIDInactive,
|
||||
clientSecret: clientSecretInactive,
|
||||
scope: []string{oidc.ScopeOpenID},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "wrong secret error",
|
||||
clientID: clientID,
|
||||
|
@ -213,7 +213,7 @@ func (s *Server) clientFromCredentials(ctx context.Context, cc *op.ClientCredent
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err = s.query.GetIntrospectionClientByID(ctx, clientID, assertion)
|
||||
client, err = s.query.ActiveIntrospectionClientByID(ctx, clientID, assertion)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, oidc.ErrUnauthorizedClient().WithParent(err).WithReturnParentToClient(authz.GetFeatures(ctx).DebugOIDCParentError)
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ import (
|
||||
"github.com/go-jose/go-jose/v4"
|
||||
"github.com/jonboulle/clockwork"
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/zitadel/logging"
|
||||
"github.com/zitadel/oidc/v3/pkg/op"
|
||||
|
||||
@ -351,14 +350,14 @@ func (o *OPStorage) getSigningKey(ctx context.Context) (op.SigningKey, error) {
|
||||
if len(keys.Keys) > 0 {
|
||||
return o.privateKeyToSigningKey(selectSigningKey(keys.Keys))
|
||||
}
|
||||
var position decimal.Decimal
|
||||
var position float64
|
||||
if keys.State != nil {
|
||||
position = keys.State.Position
|
||||
}
|
||||
return nil, o.refreshSigningKey(ctx, o.signingKeyAlgorithm, position)
|
||||
}
|
||||
|
||||
func (o *OPStorage) refreshSigningKey(ctx context.Context, algorithm string, position decimal.Decimal) error {
|
||||
func (o *OPStorage) refreshSigningKey(ctx context.Context, algorithm string, position float64) error {
|
||||
ok, err := o.ensureIsLatestKey(ctx, position)
|
||||
if err != nil || !ok {
|
||||
return zerrors.ThrowInternal(err, "OIDC-ASfh3", "cannot ensure that projection is up to date")
|
||||
@ -370,12 +369,12 @@ func (o *OPStorage) refreshSigningKey(ctx context.Context, algorithm string, pos
|
||||
return zerrors.ThrowInternal(nil, "OIDC-Df1bh", "")
|
||||
}
|
||||
|
||||
func (o *OPStorage) ensureIsLatestKey(ctx context.Context, position decimal.Decimal) (bool, error) {
|
||||
func (o *OPStorage) ensureIsLatestKey(ctx context.Context, position float64) (bool, error) {
|
||||
maxSequence, err := o.getMaxKeySequence(ctx)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error retrieving new events: %w", err)
|
||||
}
|
||||
return position.GreaterThanOrEqual(maxSequence), nil
|
||||
return position >= maxSequence, nil
|
||||
}
|
||||
|
||||
func (o *OPStorage) privateKeyToSigningKey(key query.PrivateKey) (_ op.SigningKey, err error) {
|
||||
@ -413,9 +412,9 @@ func (o *OPStorage) lockAndGenerateSigningKeyPair(ctx context.Context, algorithm
|
||||
return o.command.GenerateSigningKeyPair(setOIDCCtx(ctx), algorithm)
|
||||
}
|
||||
|
||||
func (o *OPStorage) getMaxKeySequence(ctx context.Context) (decimal.Decimal, error) {
|
||||
return o.eventstore.LatestPosition(ctx,
|
||||
eventstore.NewSearchQueryBuilder(eventstore.ColumnsMaxPosition).
|
||||
func (o *OPStorage) getMaxKeySequence(ctx context.Context) (float64, error) {
|
||||
return o.eventstore.LatestSequence(ctx,
|
||||
eventstore.NewSearchQueryBuilder(eventstore.ColumnsMaxSequence).
|
||||
ResourceOwner(authz.GetInstance(ctx).InstanceID()).
|
||||
AwaitOpenTransactions().
|
||||
AllowTimeTravel().
|
||||
|
@ -66,7 +66,10 @@ func (s *Server) UserInfo(ctx context.Context, r *op.Request[oidc.UserInfoReques
|
||||
false,
|
||||
)(ctx, true, domain.TriggerTypePreUserinfoCreation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if !zerrors.IsNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
return nil, op.NewStatusError(oidc.ErrAccessDenied().WithDescription("no active user").WithParent(err).WithReturnParentToClient(authz.GetFeatures(ctx).DebugOIDCParentError), http.StatusUnauthorized)
|
||||
}
|
||||
return op.NewResponse(userInfo), nil
|
||||
}
|
||||
@ -163,11 +166,11 @@ func prepareRoles(ctx context.Context, scope []string, projectID string, project
|
||||
}
|
||||
|
||||
func userInfoToOIDC(user *query.OIDCUserInfo, userInfoAssertion bool, scope []string, assetPrefix string) *oidc.UserInfo {
|
||||
out := new(oidc.UserInfo)
|
||||
out := &oidc.UserInfo{
|
||||
Subject: user.User.ID,
|
||||
}
|
||||
for _, s := range scope {
|
||||
switch s {
|
||||
case oidc.ScopeOpenID:
|
||||
out.Subject = user.User.ID
|
||||
case oidc.ScopeEmail:
|
||||
if !userInfoAssertion {
|
||||
continue
|
||||
|
@ -280,14 +280,18 @@ func Test_userInfoToOIDC(t *testing.T) {
|
||||
args: args{
|
||||
user: humanUserInfo,
|
||||
},
|
||||
want: &oidc.UserInfo{},
|
||||
want: &oidc.UserInfo{
|
||||
Subject: "human1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "machine, empty",
|
||||
args: args{
|
||||
user: machineUserInfo,
|
||||
},
|
||||
want: &oidc.UserInfo{},
|
||||
want: &oidc.UserInfo{
|
||||
Subject: "machine1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "human, scope openid",
|
||||
@ -317,6 +321,7 @@ func Test_userInfoToOIDC(t *testing.T) {
|
||||
scope: []string{oidc.ScopeEmail},
|
||||
},
|
||||
want: &oidc.UserInfo{
|
||||
Subject: "human1",
|
||||
UserInfoEmail: oidc.UserInfoEmail{
|
||||
Email: "foo@bar.com",
|
||||
EmailVerified: true,
|
||||
@ -329,7 +334,9 @@ func Test_userInfoToOIDC(t *testing.T) {
|
||||
user: humanUserInfo,
|
||||
scope: []string{oidc.ScopeEmail},
|
||||
},
|
||||
want: &oidc.UserInfo{},
|
||||
want: &oidc.UserInfo{
|
||||
Subject: "human1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "machine, scope email, profileInfoAssertion",
|
||||
@ -338,7 +345,7 @@ func Test_userInfoToOIDC(t *testing.T) {
|
||||
scope: []string{oidc.ScopeEmail},
|
||||
},
|
||||
want: &oidc.UserInfo{
|
||||
UserInfoEmail: oidc.UserInfoEmail{},
|
||||
Subject: "machine1",
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -349,6 +356,7 @@ func Test_userInfoToOIDC(t *testing.T) {
|
||||
scope: []string{oidc.ScopeProfile},
|
||||
},
|
||||
want: &oidc.UserInfo{
|
||||
Subject: "human1",
|
||||
UserInfoProfile: oidc.UserInfoProfile{
|
||||
Name: "xxx",
|
||||
GivenName: "user",
|
||||
@ -370,6 +378,7 @@ func Test_userInfoToOIDC(t *testing.T) {
|
||||
scope: []string{oidc.ScopeProfile},
|
||||
},
|
||||
want: &oidc.UserInfo{
|
||||
Subject: "machine1",
|
||||
UserInfoProfile: oidc.UserInfoProfile{
|
||||
Name: "machine",
|
||||
UpdatedAt: oidc.FromTime(time.Unix(567, 890)),
|
||||
@ -383,7 +392,9 @@ func Test_userInfoToOIDC(t *testing.T) {
|
||||
user: machineUserInfo,
|
||||
scope: []string{oidc.ScopeProfile},
|
||||
},
|
||||
want: &oidc.UserInfo{},
|
||||
want: &oidc.UserInfo{
|
||||
Subject: "machine1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "human, scope phone, profileInfoAssertion",
|
||||
@ -393,6 +404,7 @@ func Test_userInfoToOIDC(t *testing.T) {
|
||||
scope: []string{oidc.ScopePhone},
|
||||
},
|
||||
want: &oidc.UserInfo{
|
||||
Subject: "human1",
|
||||
UserInfoPhone: oidc.UserInfoPhone{
|
||||
PhoneNumber: "+31123456789",
|
||||
PhoneNumberVerified: true,
|
||||
@ -405,7 +417,9 @@ func Test_userInfoToOIDC(t *testing.T) {
|
||||
user: humanUserInfo,
|
||||
scope: []string{oidc.ScopePhone},
|
||||
},
|
||||
want: &oidc.UserInfo{},
|
||||
want: &oidc.UserInfo{
|
||||
Subject: "human1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "machine, scope phone",
|
||||
@ -414,6 +428,7 @@ func Test_userInfoToOIDC(t *testing.T) {
|
||||
scope: []string{oidc.ScopePhone},
|
||||
},
|
||||
want: &oidc.UserInfo{
|
||||
Subject: "machine1",
|
||||
UserInfoPhone: oidc.UserInfoPhone{},
|
||||
},
|
||||
},
|
||||
@ -424,6 +439,8 @@ func Test_userInfoToOIDC(t *testing.T) {
|
||||
scope: []string{ScopeUserMetaData},
|
||||
},
|
||||
want: &oidc.UserInfo{
|
||||
Subject: "human1",
|
||||
UserInfoEmail: oidc.UserInfoEmail{},
|
||||
Claims: map[string]any{
|
||||
ClaimUserMetaData: map[string]string{
|
||||
"key1": base64.RawURLEncoding.EncodeToString([]byte{1, 2, 3}),
|
||||
@ -438,7 +455,9 @@ func Test_userInfoToOIDC(t *testing.T) {
|
||||
user: machineUserInfo,
|
||||
scope: []string{ScopeUserMetaData},
|
||||
},
|
||||
want: &oidc.UserInfo{},
|
||||
want: &oidc.UserInfo{
|
||||
Subject: "machine1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "machine, scope resource owner",
|
||||
@ -447,6 +466,7 @@ func Test_userInfoToOIDC(t *testing.T) {
|
||||
scope: []string{ScopeResourceOwner},
|
||||
},
|
||||
want: &oidc.UserInfo{
|
||||
Subject: "machine1",
|
||||
Claims: map[string]any{
|
||||
ClaimResourceOwnerID: "orgID",
|
||||
ClaimResourceOwnerName: "orgName",
|
||||
@ -461,6 +481,7 @@ func Test_userInfoToOIDC(t *testing.T) {
|
||||
scope: []string{domain.OrgDomainPrimaryScope + "foo.com"},
|
||||
},
|
||||
want: &oidc.UserInfo{
|
||||
Subject: "human1",
|
||||
Claims: map[string]any{
|
||||
domain.OrgDomainPrimaryClaim: "foo.com",
|
||||
},
|
||||
@ -473,6 +494,7 @@ func Test_userInfoToOIDC(t *testing.T) {
|
||||
scope: []string{domain.OrgIDScope + "orgID"},
|
||||
},
|
||||
want: &oidc.UserInfo{
|
||||
Subject: "machine1",
|
||||
Claims: map[string]any{
|
||||
domain.OrgIDClaim: "orgID",
|
||||
ClaimResourceOwnerID: "orgID",
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/go-jose/go-jose/v4"
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/zitadel/logging"
|
||||
"github.com/zitadel/saml/pkg/provider/key"
|
||||
|
||||
@ -77,7 +76,7 @@ func (p *Storage) getCertificateAndKey(ctx context.Context, usage crypto.KeyUsag
|
||||
return p.certificateToCertificateAndKey(selectCertificate(certs.Certificates))
|
||||
}
|
||||
|
||||
var position decimal.Decimal
|
||||
var position float64
|
||||
if certs.State != nil {
|
||||
position = certs.State.Position
|
||||
}
|
||||
@ -88,7 +87,7 @@ func (p *Storage) getCertificateAndKey(ctx context.Context, usage crypto.KeyUsag
|
||||
func (p *Storage) refreshCertificate(
|
||||
ctx context.Context,
|
||||
usage crypto.KeyUsage,
|
||||
position decimal.Decimal,
|
||||
position float64,
|
||||
) error {
|
||||
ok, err := p.ensureIsLatestCertificate(ctx, position)
|
||||
if err != nil {
|
||||
@ -104,12 +103,12 @@ func (p *Storage) refreshCertificate(
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Storage) ensureIsLatestCertificate(ctx context.Context, position decimal.Decimal) (bool, error) {
|
||||
func (p *Storage) ensureIsLatestCertificate(ctx context.Context, position float64) (bool, error) {
|
||||
maxSequence, err := p.getMaxKeySequence(ctx)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error retrieving new events: %w", err)
|
||||
}
|
||||
return position.GreaterThanOrEqual(maxSequence), nil
|
||||
return position >= maxSequence, nil
|
||||
}
|
||||
|
||||
func (p *Storage) lockAndGenerateCertificateAndKey(ctx context.Context, usage crypto.KeyUsage) error {
|
||||
@ -152,9 +151,9 @@ func (p *Storage) lockAndGenerateCertificateAndKey(ctx context.Context, usage cr
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Storage) getMaxKeySequence(ctx context.Context) (decimal.Decimal, error) {
|
||||
return p.eventstore.LatestPosition(ctx,
|
||||
eventstore.NewSearchQueryBuilder(eventstore.ColumnsMaxPosition).
|
||||
func (p *Storage) getMaxKeySequence(ctx context.Context) (float64, error) {
|
||||
return p.eventstore.LatestSequence(ctx,
|
||||
eventstore.NewSearchQueryBuilder(eventstore.ColumnsMaxSequence).
|
||||
ResourceOwner(authz.GetInstance(ctx).InstanceID()).
|
||||
AwaitOpenTransactions().
|
||||
AddQuery().
|
||||
|
@ -55,13 +55,10 @@ type Storage struct {
|
||||
}
|
||||
|
||||
func (p *Storage) GetEntityByID(ctx context.Context, entityID string) (*serviceprovider.ServiceProvider, error) {
|
||||
app, err := p.query.AppBySAMLEntityID(ctx, entityID)
|
||||
app, err := p.query.ActiveAppBySAMLEntityID(ctx, entityID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if app.State != domain.AppStateActive {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "SAML-sdaGg", "app is not active")
|
||||
}
|
||||
return serviceprovider.NewServiceProvider(
|
||||
app.ID,
|
||||
&serviceprovider.Config{
|
||||
@ -72,13 +69,10 @@ func (p *Storage) GetEntityByID(ctx context.Context, entityID string) (*servicep
|
||||
}
|
||||
|
||||
func (p *Storage) GetEntityIDByAppID(ctx context.Context, appID string) (string, error) {
|
||||
app, err := p.query.AppByID(ctx, appID)
|
||||
app, err := p.query.AppByID(ctx, appID, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if app.State != domain.AppStateActive {
|
||||
return "", zerrors.ThrowPreconditionFailed(nil, "SAML-sdaGg", "app is not active")
|
||||
}
|
||||
return app.SAMLConfig.EntityID, nil
|
||||
}
|
||||
|
||||
@ -137,6 +131,9 @@ func (p *Storage) SetUserinfoWithUserID(ctx context.Context, applicationID strin
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if user.State != domain.UserStateActive {
|
||||
return zerrors.ThrowPreconditionFailed(nil, "SAML-S3gFd", "Errors.User.NotActive")
|
||||
}
|
||||
|
||||
userGrants, err := p.getGrants(ctx, userID, applicationID)
|
||||
if err != nil {
|
||||
@ -163,6 +160,9 @@ func (p *Storage) SetUserinfoWithLoginName(ctx context.Context, userinfo models.
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if user.State != domain.UserStateActive {
|
||||
return zerrors.ThrowPreconditionFailed(nil, "SAML-FJ262", "Errors.User.NotActive")
|
||||
}
|
||||
|
||||
setUserinfo(user, userinfo, attributes, map[string]*customAttribute{})
|
||||
return nil
|
||||
@ -330,10 +330,15 @@ func (p *Storage) getGrants(ctx context.Context, userID, applicationID string) (
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
activeQuery, err := query.NewUserGrantStateQuery(domain.UserGrantStateActive)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.query.UserGrants(ctx, &query.UserGrantsQueries{
|
||||
Queries: []query.SearchQuery{
|
||||
projectQuery,
|
||||
userIDQuery,
|
||||
activeQuery,
|
||||
},
|
||||
}, true)
|
||||
}
|
||||
|
@ -1092,7 +1092,7 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *domain.Auth
|
||||
}
|
||||
if !user.IsEmailVerified {
|
||||
steps = append(steps, &domain.VerifyEMailStep{
|
||||
InitPassword: !user.PasswordSet,
|
||||
InitPassword: !user.PasswordSet && len(idps.Links) == 0,
|
||||
})
|
||||
}
|
||||
if user.UsernameChangeRequired {
|
||||
|
@ -1058,6 +1058,74 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
||||
[]domain.NextStep{&domain.VerifyInviteStep{}},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"password not set (email not verified), verify email with password step",
|
||||
fields{
|
||||
userSessionViewProvider: &mockViewUserSession{},
|
||||
userViewProvider: &mockViewUser{
|
||||
PasswordInitRequired: true,
|
||||
},
|
||||
userEventProvider: &mockEventUser{},
|
||||
lockoutPolicyProvider: &mockLockoutPolicy{
|
||||
policy: &query.LockoutPolicy{
|
||||
ShowFailures: true,
|
||||
},
|
||||
},
|
||||
orgViewProvider: &mockViewOrg{State: domain.OrgStateActive},
|
||||
idpUserLinksProvider: &mockIDPUserLinks{},
|
||||
},
|
||||
args{
|
||||
&domain.AuthRequest{
|
||||
UserID: "UserID",
|
||||
LoginPolicy: &domain.LoginPolicy{
|
||||
AllowUsernamePassword: true,
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
[]domain.NextStep{&domain.VerifyEMailStep{InitPassword: true}},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"password not set, but idp, email not verified, verify email step",
|
||||
fields{
|
||||
userSessionViewProvider: &mockViewUserSession{
|
||||
ExternalLoginVerification: testNow.Add(-5 * time.Minute),
|
||||
},
|
||||
userViewProvider: &mockViewUser{},
|
||||
userEventProvider: &mockEventUser{},
|
||||
lockoutPolicyProvider: &mockLockoutPolicy{
|
||||
policy: &query.LockoutPolicy{
|
||||
ShowFailures: true,
|
||||
},
|
||||
},
|
||||
orgViewProvider: &mockViewOrg{State: domain.OrgStateActive},
|
||||
idpUserLinksProvider: &mockIDPUserLinks{
|
||||
[]*query.IDPUserLink{
|
||||
{
|
||||
IDPID: "idpID",
|
||||
UserID: "userID",
|
||||
IDPName: "idpName",
|
||||
ProvidedUserID: "providedUserID",
|
||||
ProvidedUsername: "providedUsername",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
args{
|
||||
&domain.AuthRequest{
|
||||
UserID: "UserID",
|
||||
LoginPolicy: &domain.LoginPolicy{
|
||||
AllowUsernamePassword: true,
|
||||
ExternalLoginCheckLifetime: 10 * 24 * time.Hour,
|
||||
},
|
||||
SelectedIDPConfigID: "idpID",
|
||||
},
|
||||
false,
|
||||
},
|
||||
[]domain.NextStep{&domain.VerifyEMailStep{}},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"password not set (email not verified), init password step",
|
||||
fields{
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
sd "github.com/zitadel/zitadel/internal/config/systemdefaults"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
eventstore2 "github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/id"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
@ -119,7 +120,11 @@ func (q queryViewWrapper) UserGrantsByProjectAndUserID(ctx context.Context, proj
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queries := &query.UserGrantsQueries{Queries: []query.SearchQuery{userGrantUserID, userGrantProjectID}}
|
||||
activeQuery, err := query.NewUserGrantStateQuery(domain.UserGrantStateActive)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queries := &query.UserGrantsQueries{Queries: []query.SearchQuery{userGrantUserID, userGrantProjectID, activeQuery}}
|
||||
grants, err := q.Queries.UserGrants(ctx, queries, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -144,7 +144,7 @@ func (c *Commands) CreateOIDCSessionFromDeviceAuth(ctx context.Context, deviceCo
|
||||
return nil, DeviceAuthStateError(deviceAuthModel.State)
|
||||
}
|
||||
|
||||
cmd, err := c.newOIDCSessionAddEvents(ctx, deviceAuthModel.UserOrgID)
|
||||
cmd, err := c.newOIDCSessionAddEvents(ctx, deviceAuthModel.UserID, deviceAuthModel.UserOrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -126,7 +126,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
|
||||
pushErr := errors.New("pushErr")
|
||||
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@ -149,7 +149,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
|
||||
{
|
||||
name: "not found error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
@ -169,7 +169,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
|
||||
{
|
||||
name: "push error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(eventFromEventPusherWithInstanceID(
|
||||
"instance1",
|
||||
deviceauth.NewAddedEvent(
|
||||
@ -211,7 +211,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
|
||||
{
|
||||
name: "success",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(eventFromEventPusherWithInstanceID(
|
||||
"instance1",
|
||||
deviceauth.NewAddedEvent(
|
||||
@ -256,7 +256,7 @@ func TestCommands_ApproveDeviceAuth(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
}
|
||||
gotDetails, err := c.ApproveDeviceAuth(tt.args.ctx, tt.args.id, tt.args.userID, tt.args.userOrgID, tt.args.authMethods, tt.args.authTime, tt.args.preferredLanguage, tt.args.userAgent, tt.args.sessionID)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
@ -271,7 +271,7 @@ func TestCommands_CancelDeviceAuth(t *testing.T) {
|
||||
pushErr := errors.New("pushErr")
|
||||
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@ -288,7 +288,7 @@ func TestCommands_CancelDeviceAuth(t *testing.T) {
|
||||
{
|
||||
name: "not found error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
@ -298,7 +298,7 @@ func TestCommands_CancelDeviceAuth(t *testing.T) {
|
||||
{
|
||||
name: "push error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(eventFromEventPusherWithInstanceID(
|
||||
"instance1",
|
||||
deviceauth.NewAddedEvent(
|
||||
@ -323,7 +323,7 @@ func TestCommands_CancelDeviceAuth(t *testing.T) {
|
||||
{
|
||||
name: "success/denied",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(eventFromEventPusherWithInstanceID(
|
||||
"instance1",
|
||||
deviceauth.NewAddedEvent(
|
||||
@ -350,7 +350,7 @@ func TestCommands_CancelDeviceAuth(t *testing.T) {
|
||||
{
|
||||
name: "success/expired",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(eventFromEventPusherWithInstanceID(
|
||||
"instance1",
|
||||
deviceauth.NewAddedEvent(
|
||||
@ -378,7 +378,7 @@ func TestCommands_CancelDeviceAuth(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
}
|
||||
gotDetails, err := c.CancelDeviceAuth(tt.args.ctx, tt.args.id, tt.args.reason)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
@ -586,6 +586,69 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
|
||||
},
|
||||
wantErr: DeviceAuthStateError(domain.DeviceAuthStateDone),
|
||||
},
|
||||
{
|
||||
name: "user not active",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusherWithInstanceID(
|
||||
"instance1",
|
||||
deviceauth.NewAddedEvent(
|
||||
ctx,
|
||||
deviceauth.NewAggregate("123", "instance1"),
|
||||
"clientID", "123", "456", time.Now().Add(-time.Minute),
|
||||
[]string{"openid", "offline_access"},
|
||||
[]string{"audience"}, false,
|
||||
),
|
||||
),
|
||||
eventFromEventPusherWithInstanceID(
|
||||
"instance1",
|
||||
deviceauth.NewApprovedEvent(ctx,
|
||||
deviceauth.NewAggregate("123", "instance1"),
|
||||
"userID", "org1",
|
||||
[]domain.UserAuthMethodType{domain.UserAuthMethodTypePassword},
|
||||
testNow, &language.Afrikaans, &domain.UserAgent{
|
||||
FingerprintID: gu.Ptr("fp1"),
|
||||
IP: net.ParseIP("1.2.3.4"),
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{"foo": []string{"bar"}},
|
||||
},
|
||||
"sessionID",
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
user.NewHumanAddedEvent(
|
||||
ctx,
|
||||
&user.NewAggregate("userID", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"nickname",
|
||||
"displayname",
|
||||
language.English,
|
||||
domain.GenderUnspecified,
|
||||
"email",
|
||||
false,
|
||||
),
|
||||
user.NewUserDeactivatedEvent(
|
||||
ctx,
|
||||
&user.NewAggregate("userID", "org1").Aggregate,
|
||||
),
|
||||
),
|
||||
),
|
||||
idGenerator: mock.NewIDGeneratorExpectIDs(t),
|
||||
defaultAccessTokenLifetime: time.Hour,
|
||||
defaultRefreshTokenLifetime: 7 * 24 * time.Hour,
|
||||
defaultRefreshTokenIdleLifetime: 24 * time.Hour,
|
||||
keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
ctx,
|
||||
"123",
|
||||
},
|
||||
wantErr: zerrors.ThrowPreconditionFailed(nil, "OIDCS-kj3g2", "Errors.User.NotActive"),
|
||||
},
|
||||
{
|
||||
name: "approved, success",
|
||||
fields: fields{
|
||||
@ -617,6 +680,21 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
user.NewHumanAddedEvent(
|
||||
ctx,
|
||||
&user.NewAggregate("userID", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"nickname",
|
||||
"displayname",
|
||||
language.English,
|
||||
domain.GenderUnspecified,
|
||||
"email",
|
||||
false,
|
||||
),
|
||||
),
|
||||
expectFilter(), // token lifetime
|
||||
expectPush(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
@ -699,6 +777,21 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
user.NewHumanAddedEvent(
|
||||
ctx,
|
||||
&user.NewAggregate("userID", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"nickname",
|
||||
"displayname",
|
||||
language.English,
|
||||
domain.GenderUnspecified,
|
||||
"email",
|
||||
false,
|
||||
),
|
||||
),
|
||||
expectFilter(), // token lifetime
|
||||
expectPush(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
|
@ -104,6 +104,7 @@ func (wm *IAMSMTPConfigWriteModel) Reduce() error {
|
||||
wm.reduceSMTPConfigRemovedEvent(e)
|
||||
case *instance.SMTPConfigActivatedEvent:
|
||||
if wm.ID != e.ID {
|
||||
wm.State = domain.SMTPConfigStateInactive
|
||||
continue
|
||||
}
|
||||
wm.State = domain.SMTPConfigStateActive
|
||||
|
@ -80,7 +80,7 @@ func (c *Commands) CreateOIDCSessionFromAuthRequest(ctx context.Context, authReq
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
cmd, err := c.newOIDCSessionAddEvents(ctx, sessionModel.UserResourceOwner)
|
||||
cmd, err := c.newOIDCSessionAddEvents(ctx, sessionModel.UserID, sessionModel.UserResourceOwner)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
@ -141,7 +141,7 @@ func (c *Commands) CreateOIDCSession(ctx context.Context,
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
cmd, err := c.newOIDCSessionAddEvents(ctx, resourceOwner)
|
||||
cmd, err := c.newOIDCSessionAddEvents(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -265,7 +265,14 @@ func (c *Commands) RevokeOIDCSessionToken(ctx context.Context, token, clientID s
|
||||
return c.pushAppendAndReduce(ctx, writeModel, oidcsession.NewAccessTokenRevokedEvent(ctx, writeModel.aggregate))
|
||||
}
|
||||
|
||||
func (c *Commands) newOIDCSessionAddEvents(ctx context.Context, resourceOwner string, pending ...eventstore.Command) (*OIDCSessionEvents, error) {
|
||||
func (c *Commands) newOIDCSessionAddEvents(ctx context.Context, userID, resourceOwner string, pending ...eventstore.Command) (*OIDCSessionEvents, error) {
|
||||
userStateModel, err := c.userStateWriteModel(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !userStateModel.UserState.IsEnabled() {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "OIDCS-kj3g2", "Errors.User.NotActive")
|
||||
}
|
||||
accessTokenLifetime, refreshTokenLifeTime, refreshTokenIdleLifetime, err := c.tokenTokenLifetimes(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -281,6 +288,7 @@ func (c *Commands) newOIDCSessionAddEvents(ctx context.Context, resourceOwner st
|
||||
encryptionAlg: c.keyAlgorithm,
|
||||
events: pending,
|
||||
oidcSessionWriteModel: NewOIDCSessionWriteModel(sessionID, resourceOwner),
|
||||
userStateModel: userStateModel,
|
||||
accessTokenLifetime: accessTokenLifetime,
|
||||
refreshTokenLifeTime: refreshTokenLifeTime,
|
||||
refreshTokenIdleLifetime: refreshTokenIdleLifetime,
|
||||
@ -321,6 +329,13 @@ func (c *Commands) newOIDCSessionUpdateEvents(ctx context.Context, refreshToken
|
||||
if err = sessionWriteModel.CheckRefreshToken(refreshTokenID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userStateWriteModel, err := c.userStateWriteModel(ctx, sessionWriteModel.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !userStateWriteModel.UserState.IsEnabled() {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "OIDCS-J39h2", "Errors.User.NotActive")
|
||||
}
|
||||
accessTokenLifetime, refreshTokenLifeTime, refreshTokenIdleLifetime, err := c.tokenTokenLifetimes(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -342,6 +357,7 @@ type OIDCSessionEvents struct {
|
||||
encryptionAlg crypto.EncryptionAlgorithm
|
||||
events []eventstore.Command
|
||||
oidcSessionWriteModel *OIDCSessionWriteModel
|
||||
userStateModel *UserV2WriteModel
|
||||
|
||||
accessTokenLifetime time.Duration
|
||||
refreshTokenLifeTime time.Duration
|
||||
|
@ -205,6 +205,103 @@ func TestCommands_CreateOIDCSessionFromAuthRequest(t *testing.T) {
|
||||
err: zerrors.ThrowPreconditionFailed(nil, "COMMAND-Flk38", "Errors.Session.NotExisting"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"user not active",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
authrequest.NewAddedEvent(context.Background(), &authrequest.NewAggregate("V2_authRequestID", "instanceID").Aggregate,
|
||||
"loginClient",
|
||||
"clientID",
|
||||
"redirectURI",
|
||||
"state",
|
||||
"nonce",
|
||||
[]string{"openid", "offline_access"},
|
||||
[]string{"audience"},
|
||||
domain.OIDCResponseTypeCode,
|
||||
domain.OIDCResponseModeQuery,
|
||||
&domain.OIDCCodeChallenge{
|
||||
Challenge: "challenge",
|
||||
Method: domain.CodeChallengeMethodS256,
|
||||
},
|
||||
[]domain.Prompt{domain.PromptNone},
|
||||
[]string{"en", "de"},
|
||||
gu.Ptr(time.Duration(0)),
|
||||
gu.Ptr("loginHint"),
|
||||
gu.Ptr("hintUserID"),
|
||||
true,
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
authrequest.NewCodeAddedEvent(context.Background(), &authrequest.NewAggregate("V2_authRequestID", "instanceID").Aggregate),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
authrequest.NewSessionLinkedEvent(context.Background(), &authrequest.NewAggregate("V2_authRequestID", "instanceID").Aggregate,
|
||||
"sessionID",
|
||||
"userID",
|
||||
testNow,
|
||||
[]domain.UserAuthMethodType{domain.UserAuthMethodTypePassword},
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
session.NewAddedEvent(context.Background(),
|
||||
&session.NewAggregate("sessionID", "instance1").Aggregate,
|
||||
&domain.UserAgent{
|
||||
FingerprintID: gu.Ptr("fp1"),
|
||||
IP: net.ParseIP("1.2.3.4"),
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{"foo": []string{"bar"}},
|
||||
},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
session.NewUserCheckedEvent(context.Background(), &session.NewAggregate("sessionID", "instanceID").Aggregate,
|
||||
"userID", "org1", testNow, &language.Afrikaans),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
session.NewPasswordCheckedEvent(context.Background(), &session.NewAggregate("sessionID", "instanceID").Aggregate,
|
||||
testNow),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
user.NewHumanAddedEvent(
|
||||
context.Background(),
|
||||
&user.NewAggregate("userID", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"nickname",
|
||||
"displayname",
|
||||
language.Afrikaans,
|
||||
domain.GenderUnspecified,
|
||||
"email",
|
||||
false,
|
||||
),
|
||||
user.NewUserDeactivatedEvent(
|
||||
context.Background(),
|
||||
&user.NewAggregate("userID", "org1").Aggregate,
|
||||
),
|
||||
),
|
||||
),
|
||||
idGenerator: mock.NewIDGeneratorExpectIDs(t),
|
||||
defaultAccessTokenLifetime: time.Hour,
|
||||
defaultRefreshTokenLifetime: 7 * 24 * time.Hour,
|
||||
defaultRefreshTokenIdleLifetime: 24 * time.Hour,
|
||||
keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instanceID"),
|
||||
authRequestID: "V2_authRequestID",
|
||||
complianceCheck: mockAuthRequestComplianceChecker(nil),
|
||||
needRefreshToken: true,
|
||||
},
|
||||
res{
|
||||
err: zerrors.ThrowPreconditionFailed(nil, "OIDCS-kj3g2", "Errors.User.NotActive"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"add successful",
|
||||
fields{
|
||||
@ -266,6 +363,21 @@ func TestCommands_CreateOIDCSessionFromAuthRequest(t *testing.T) {
|
||||
testNow),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
user.NewHumanAddedEvent(
|
||||
context.Background(),
|
||||
&user.NewAggregate("userID", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"nickname",
|
||||
"displayname",
|
||||
language.Afrikaans,
|
||||
domain.GenderUnspecified,
|
||||
"email",
|
||||
false,
|
||||
),
|
||||
),
|
||||
expectFilter(), // token lifetime
|
||||
expectPush(
|
||||
authrequest.NewCodeExchangedEvent(context.Background(), &authrequest.NewAggregate("V2_authRequestID", "instanceID").Aggregate),
|
||||
@ -382,6 +494,21 @@ func TestCommands_CreateOIDCSessionFromAuthRequest(t *testing.T) {
|
||||
testNow),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
user.NewHumanAddedEvent(
|
||||
context.Background(),
|
||||
&user.NewAggregate("userID", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"nickname",
|
||||
"displayname",
|
||||
language.Afrikaans,
|
||||
domain.GenderUnspecified,
|
||||
"email",
|
||||
false,
|
||||
),
|
||||
),
|
||||
expectFilter(), // token lifetime
|
||||
expectPush(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
@ -521,10 +648,81 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
|
||||
},
|
||||
wantErr: io.ErrClosedPipe,
|
||||
},
|
||||
{
|
||||
name: "not active user",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
user.NewHumanAddedEvent(
|
||||
context.Background(),
|
||||
&user.NewAggregate("userID", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"nickname",
|
||||
"displayname",
|
||||
language.Afrikaans,
|
||||
domain.GenderUnspecified,
|
||||
"email",
|
||||
false,
|
||||
),
|
||||
user.NewUserDeactivatedEvent(
|
||||
context.Background(),
|
||||
&user.NewAggregate("userID", "org1").Aggregate,
|
||||
),
|
||||
),
|
||||
),
|
||||
idGenerator: mock.NewIDGeneratorExpectIDs(t),
|
||||
defaultAccessTokenLifetime: time.Hour,
|
||||
defaultRefreshTokenLifetime: 7 * 24 * time.Hour,
|
||||
defaultRefreshTokenIdleLifetime: 24 * time.Hour,
|
||||
keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "userID",
|
||||
resourceOwner: "org1",
|
||||
clientID: "clientID",
|
||||
audience: []string{"audience"},
|
||||
scope: []string{"openid", "offline_access"},
|
||||
authMethods: []domain.UserAuthMethodType{domain.UserAuthMethodTypePassword},
|
||||
authTime: testNow,
|
||||
nonce: "nonce",
|
||||
preferredLanguage: &language.Afrikaans,
|
||||
userAgent: &domain.UserAgent{
|
||||
FingerprintID: gu.Ptr("fp1"),
|
||||
IP: net.ParseIP("1.2.3.4"),
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{"foo": []string{"bar"}},
|
||||
},
|
||||
reason: domain.TokenReasonAuthRequest,
|
||||
actor: &domain.TokenActor{
|
||||
UserID: "user2",
|
||||
Issuer: "foo.com",
|
||||
},
|
||||
needRefreshToken: false,
|
||||
},
|
||||
wantErr: zerrors.ThrowPreconditionFailed(nil, "OIDCS-kj3g2", "Errors.User.NotActive"),
|
||||
},
|
||||
{
|
||||
name: "without refresh token",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
user.NewHumanAddedEvent(
|
||||
context.Background(),
|
||||
&user.NewAggregate("userID", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"nickname",
|
||||
"displayname",
|
||||
language.Afrikaans,
|
||||
domain.GenderUnspecified,
|
||||
"email",
|
||||
false,
|
||||
),
|
||||
),
|
||||
expectFilter(), // token lifetime
|
||||
expectPush(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
@ -606,6 +804,21 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
|
||||
name: "with refresh token",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
user.NewHumanAddedEvent(
|
||||
context.Background(),
|
||||
&user.NewAggregate("userID", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"nickname",
|
||||
"displayname",
|
||||
language.Afrikaans,
|
||||
domain.GenderUnspecified,
|
||||
"email",
|
||||
false,
|
||||
),
|
||||
),
|
||||
expectFilter(), // token lifetime
|
||||
expectPush(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
@ -689,6 +902,21 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
|
||||
name: "with sessionID",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
user.NewHumanAddedEvent(
|
||||
context.Background(),
|
||||
&user.NewAggregate("userID", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"nickname",
|
||||
"displayname",
|
||||
language.Afrikaans,
|
||||
domain.GenderUnspecified,
|
||||
"email",
|
||||
false,
|
||||
),
|
||||
),
|
||||
expectFilter(), // token lifetime
|
||||
expectPush(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
@ -772,6 +1000,21 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
|
||||
name: "impersonation not allowed",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
user.NewHumanAddedEvent(
|
||||
context.Background(),
|
||||
&user.NewAggregate("userID", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"nickname",
|
||||
"displayname",
|
||||
language.Afrikaans,
|
||||
domain.GenderUnspecified,
|
||||
"email",
|
||||
false,
|
||||
),
|
||||
),
|
||||
expectFilter(), // token lifetime
|
||||
),
|
||||
idGenerator: mock.NewIDGeneratorExpectIDs(t, "oidcSessionID"),
|
||||
@ -813,6 +1056,21 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
|
||||
name: "impersonation allowed",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
user.NewHumanAddedEvent(
|
||||
context.Background(),
|
||||
&user.NewAggregate("userID", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"nickname",
|
||||
"displayname",
|
||||
language.Afrikaans,
|
||||
domain.GenderUnspecified,
|
||||
"email",
|
||||
false,
|
||||
),
|
||||
),
|
||||
expectFilter(), // token lifetime
|
||||
expectPush(
|
||||
user.NewUserImpersonatedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate, "clientID", &domain.TokenActor{
|
||||
@ -1067,6 +1325,63 @@ func TestCommands_ExchangeOIDCSessionRefreshAndAccessToken(t *testing.T) {
|
||||
err: zerrors.ThrowPreconditionFailed(nil, "OIDCS-3jt2w", "Errors.OIDCSession.RefreshTokenInvalid"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"user not active",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusherWithCreationDateNow(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"userID", "org1", "sessionID", "clientID", []string{"audience"}, []string{"openid", "profile", "offline_access"},
|
||||
[]domain.UserAuthMethodType{domain.UserAuthMethodTypePassword}, testNow, "nonce", &language.Afrikaans,
|
||||
&domain.UserAgent{FingerprintID: gu.Ptr("browserFP")},
|
||||
),
|
||||
),
|
||||
eventFromEventPusherWithCreationDateNow(
|
||||
oidcsession.NewAccessTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"at_accessTokenID", []string{"openid", "profile", "offline_access"}, time.Hour, domain.TokenReasonAuthRequest, nil),
|
||||
),
|
||||
eventFromEventPusherWithCreationDateNow(
|
||||
oidcsession.NewRefreshTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"rt_refreshTokenID", 7*24*time.Hour, 24*time.Hour),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
user.NewHumanAddedEvent(
|
||||
context.Background(),
|
||||
&user.NewAggregate("userID", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"nickname",
|
||||
"displayname",
|
||||
language.Afrikaans,
|
||||
domain.GenderUnspecified,
|
||||
"email",
|
||||
false,
|
||||
),
|
||||
user.NewUserDeactivatedEvent(
|
||||
context.Background(),
|
||||
&user.NewAggregate("userID", "org1").Aggregate,
|
||||
),
|
||||
),
|
||||
),
|
||||
idGenerator: mock.NewIDGeneratorExpectIDs(t),
|
||||
defaultAccessTokenLifetime: time.Hour,
|
||||
defaultRefreshTokenLifetime: 7 * 24 * time.Hour,
|
||||
defaultRefreshTokenIdleLifetime: 24 * time.Hour,
|
||||
keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instanceID"),
|
||||
refreshToken: "VjJfb2lkY1Nlc3Npb25JRC1ydF9yZWZyZXNoVG9rZW5JRDp1c2VySUQ", //V2_oidcSessionID:rt_refreshTokenID:userID
|
||||
scope: []string{"openid", "offline_access"},
|
||||
complianceCheck: mockRefreshTokenComplianceChecker(nil),
|
||||
},
|
||||
res{
|
||||
err: zerrors.ThrowPreconditionFailed(nil, "OIDCS-J39h2", "Errors.User.NotActive"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"refresh successful",
|
||||
fields{
|
||||
@ -1088,6 +1403,21 @@ func TestCommands_ExchangeOIDCSessionRefreshAndAccessToken(t *testing.T) {
|
||||
"rt_refreshTokenID", 7*24*time.Hour, 24*time.Hour),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
user.NewHumanAddedEvent(
|
||||
context.Background(),
|
||||
&user.NewAggregate("userID", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"nickname",
|
||||
"displayname",
|
||||
language.Afrikaans,
|
||||
domain.GenderUnspecified,
|
||||
"email",
|
||||
false,
|
||||
),
|
||||
),
|
||||
expectFilter(), // token lifetime
|
||||
expectPush(
|
||||
oidcsession.NewAccessTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
@ -1153,7 +1483,7 @@ func TestCommands_ExchangeOIDCSessionRefreshAndAccessToken(t *testing.T) {
|
||||
|
||||
func TestCommands_OIDCSessionByRefreshToken(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
idGenerator id.Generator
|
||||
defaultAccessTokenLifetime time.Duration
|
||||
defaultRefreshTokenLifetime time.Duration
|
||||
@ -1177,7 +1507,7 @@ func TestCommands_OIDCSessionByRefreshToken(t *testing.T) {
|
||||
{
|
||||
"invalid refresh token format error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
eventstore: expectEventstore(),
|
||||
keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args{
|
||||
@ -1191,7 +1521,7 @@ func TestCommands_OIDCSessionByRefreshToken(t *testing.T) {
|
||||
{
|
||||
"inactive session error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
@ -1207,7 +1537,7 @@ func TestCommands_OIDCSessionByRefreshToken(t *testing.T) {
|
||||
{
|
||||
"invalid refresh token error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
@ -1235,7 +1565,7 @@ func TestCommands_OIDCSessionByRefreshToken(t *testing.T) {
|
||||
{
|
||||
"expired refresh token error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
@ -1267,7 +1597,7 @@ func TestCommands_OIDCSessionByRefreshToken(t *testing.T) {
|
||||
{
|
||||
"get successful",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusherWithCreationDateNow(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
@ -1316,7 +1646,7 @@ func TestCommands_OIDCSessionByRefreshToken(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
idGenerator: tt.fields.idGenerator,
|
||||
defaultAccessTokenLifetime: tt.fields.defaultAccessTokenLifetime,
|
||||
defaultRefreshTokenLifetime: tt.fields.defaultRefreshTokenLifetime,
|
||||
@ -1348,7 +1678,7 @@ func TestCommands_OIDCSessionByRefreshToken(t *testing.T) {
|
||||
|
||||
func TestCommands_RevokeOIDCSessionToken(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
keyAlgorithm crypto.EncryptionAlgorithm
|
||||
}
|
||||
type args struct {
|
||||
@ -1368,7 +1698,7 @@ func TestCommands_RevokeOIDCSessionToken(t *testing.T) {
|
||||
{
|
||||
"invalid token",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
eventstore: expectEventstore(),
|
||||
keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args{
|
||||
@ -1382,7 +1712,7 @@ func TestCommands_RevokeOIDCSessionToken(t *testing.T) {
|
||||
{
|
||||
"refresh_token inactive",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
@ -1407,7 +1737,7 @@ func TestCommands_RevokeOIDCSessionToken(t *testing.T) {
|
||||
{
|
||||
"refresh_token invalid client",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
@ -1432,7 +1762,7 @@ func TestCommands_RevokeOIDCSessionToken(t *testing.T) {
|
||||
{
|
||||
"refresh_token revoked",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
@ -1468,7 +1798,7 @@ func TestCommands_RevokeOIDCSessionToken(t *testing.T) {
|
||||
{
|
||||
"access_token inactive session",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
@ -1493,7 +1823,7 @@ func TestCommands_RevokeOIDCSessionToken(t *testing.T) {
|
||||
{
|
||||
"access_token invalid client",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
@ -1518,7 +1848,7 @@ func TestCommands_RevokeOIDCSessionToken(t *testing.T) {
|
||||
{
|
||||
"access_token revoked",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
@ -1555,7 +1885,7 @@ func TestCommands_RevokeOIDCSessionToken(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
keyAlgorithm: tt.fields.keyAlgorithm,
|
||||
}
|
||||
err := c.RevokeOIDCSessionToken(tt.args.ctx, tt.args.token, tt.args.clientID)
|
||||
|
@ -93,6 +93,7 @@ func (wm *IAMSMSConfigWriteModel) Reduce() error {
|
||||
}
|
||||
case *instance.SMSConfigTwilioActivatedEvent:
|
||||
if wm.ID != e.ID {
|
||||
wm.State = domain.SMSConfigStateInactive
|
||||
continue
|
||||
}
|
||||
wm.State = domain.SMSConfigStateActive
|
||||
@ -110,6 +111,7 @@ func (wm *IAMSMSConfigWriteModel) Reduce() error {
|
||||
wm.State = domain.SMSConfigStateRemoved
|
||||
case *instance.SMSConfigActivatedEvent:
|
||||
if wm.ID != e.ID {
|
||||
wm.State = domain.SMSConfigStateInactive
|
||||
continue
|
||||
}
|
||||
wm.State = domain.SMSConfigStateActive
|
||||
|
@ -7,8 +7,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
pgxdecimal "github.com/jackc/pgx-shopspring-decimal"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/jackc/pgx/v5/stdlib"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
@ -84,11 +82,6 @@ func (c *Config) Connect(useAdmin bool, pusherRatio, spoolerRatio float64, purpo
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config.AfterConnect = func(ctx context.Context, conn *pgx.Conn) error {
|
||||
pgxdecimal.Register(conn.TypeMap())
|
||||
return nil
|
||||
}
|
||||
|
||||
if connConfig.MaxOpenConns != 0 {
|
||||
config.MaxConns = int32(connConfig.MaxOpenConns)
|
||||
}
|
||||
|
@ -7,8 +7,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
pgxdecimal "github.com/jackc/pgx-shopspring-decimal"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/jackc/pgx/v5/stdlib"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
@ -84,10 +82,6 @@ func (c *Config) Connect(useAdmin bool, pusherRatio, spoolerRatio float64, purpo
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.AfterConnect = func(ctx context.Context, conn *pgx.Conn) error {
|
||||
pgxdecimal.Register(conn.TypeMap())
|
||||
return nil
|
||||
}
|
||||
|
||||
if connConfig.MaxOpenConns != 0 {
|
||||
config.MaxConns = int32(connConfig.MaxOpenConns)
|
||||
|
@ -5,8 +5,6 @@ import (
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
@ -46,7 +44,7 @@ type Event interface {
|
||||
// CreatedAt is the time the event was created at
|
||||
CreatedAt() time.Time
|
||||
// Position is the global position of the event
|
||||
Position() decimal.Decimal
|
||||
Position() float64
|
||||
|
||||
// Unmarshal parses the payload and stores the result
|
||||
// in the value pointed to by ptr. If ptr is nil or not a pointer,
|
||||
|
@ -5,8 +5,6 @@ import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/service"
|
||||
)
|
||||
@ -23,7 +21,7 @@ type BaseEvent struct {
|
||||
Agg *Aggregate
|
||||
|
||||
Seq uint64
|
||||
Pos decimal.Decimal
|
||||
Pos float64
|
||||
Creation time.Time
|
||||
previousAggregateSequence uint64
|
||||
previousAggregateTypeSequence uint64
|
||||
@ -36,7 +34,7 @@ type BaseEvent struct {
|
||||
}
|
||||
|
||||
// Position implements Event.
|
||||
func (e *BaseEvent) Position() decimal.Decimal {
|
||||
func (e *BaseEvent) Position() float64 {
|
||||
return e.Pos
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
@ -218,11 +217,11 @@ func (es *Eventstore) FilterToReducer(ctx context.Context, searchQuery *SearchQu
|
||||
})
|
||||
}
|
||||
|
||||
// LatestPosition filters the latest position for the given search query
|
||||
func (es *Eventstore) LatestPosition(ctx context.Context, queryFactory *SearchQueryBuilder) (decimal.Decimal, error) {
|
||||
// LatestSequence filters the latest sequence for the given search query
|
||||
func (es *Eventstore) LatestSequence(ctx context.Context, queryFactory *SearchQueryBuilder) (float64, error) {
|
||||
queryFactory.InstanceID(authz.GetInstance(ctx).InstanceID())
|
||||
|
||||
return es.querier.LatestPosition(ctx, queryFactory)
|
||||
return es.querier.LatestSequence(ctx, queryFactory)
|
||||
}
|
||||
|
||||
// InstanceIDs returns the instance ids found by the search query
|
||||
@ -267,8 +266,8 @@ type Querier interface {
|
||||
Health(ctx context.Context) error
|
||||
// FilterToReducer calls r for every event returned from the storage
|
||||
FilterToReducer(ctx context.Context, searchQuery *SearchQueryBuilder, r Reducer) error
|
||||
// LatestPosition returns the latest position found by the search query
|
||||
LatestPosition(ctx context.Context, queryFactory *SearchQueryBuilder) (decimal.Decimal, error)
|
||||
// LatestSequence returns the latest sequence found by the search query
|
||||
LatestSequence(ctx context.Context, queryFactory *SearchQueryBuilder) (float64, error)
|
||||
// InstanceIDs returns the instance ids found by the search query
|
||||
InstanceIDs(ctx context.Context, queryFactory *SearchQueryBuilder) ([]string, error)
|
||||
}
|
||||
|
@ -4,8 +4,6 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
)
|
||||
|
||||
@ -100,7 +98,7 @@ func TestCRDB_Filter(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCRDB_LatestPosition(t *testing.T) {
|
||||
func TestCRDB_LatestSequence(t *testing.T) {
|
||||
type args struct {
|
||||
searchQuery *eventstore.SearchQueryBuilder
|
||||
}
|
||||
@ -108,7 +106,7 @@ func TestCRDB_LatestPosition(t *testing.T) {
|
||||
existingEvents []eventstore.Command
|
||||
}
|
||||
type res struct {
|
||||
position decimal.Decimal
|
||||
sequence float64
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@ -120,7 +118,7 @@ func TestCRDB_LatestPosition(t *testing.T) {
|
||||
{
|
||||
name: "aggregate type filter no sequence",
|
||||
args: args{
|
||||
searchQuery: eventstore.NewSearchQueryBuilder(eventstore.ColumnsMaxPosition).
|
||||
searchQuery: eventstore.NewSearchQueryBuilder(eventstore.ColumnsMaxSequence).
|
||||
AddQuery().
|
||||
AggregateTypes("not found").
|
||||
Builder(),
|
||||
@ -137,7 +135,7 @@ func TestCRDB_LatestPosition(t *testing.T) {
|
||||
{
|
||||
name: "aggregate type filter sequence",
|
||||
args: args{
|
||||
searchQuery: eventstore.NewSearchQueryBuilder(eventstore.ColumnsMaxPosition).
|
||||
searchQuery: eventstore.NewSearchQueryBuilder(eventstore.ColumnsMaxSequence).
|
||||
AddQuery().
|
||||
AggregateTypes(eventstore.AggregateType(t.Name())).
|
||||
Builder(),
|
||||
@ -171,12 +169,12 @@ func TestCRDB_LatestPosition(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
position, err := db.LatestPosition(context.Background(), tt.args.searchQuery)
|
||||
sequence, err := db.LatestSequence(context.Background(), tt.args.searchQuery)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("CRDB.query() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if tt.res.position.GreaterThan(position) {
|
||||
t.Errorf("CRDB.query() expected sequence: %v got %v", tt.res.position, position)
|
||||
if tt.res.sequence > sequence {
|
||||
t.Errorf("CRDB.query() expected sequence: %v got %v", tt.res.sequence, sequence)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
"github.com/shopspring/decimal"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/service"
|
||||
@ -391,7 +390,7 @@ func (repo *testPusher) Push(ctx context.Context, commands ...Command) (events [
|
||||
|
||||
type testQuerier struct {
|
||||
events []Event
|
||||
sequence decimal.Decimal
|
||||
sequence float64
|
||||
instances []string
|
||||
err error
|
||||
t *testing.T
|
||||
@ -424,9 +423,9 @@ func (repo *testQuerier) FilterToReducer(ctx context.Context, searchQuery *Searc
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repo *testQuerier) LatestPosition(ctx context.Context, queryFactory *SearchQueryBuilder) (decimal.Decimal, error) {
|
||||
func (repo *testQuerier) LatestSequence(ctx context.Context, queryFactory *SearchQueryBuilder) (float64, error) {
|
||||
if repo.err != nil {
|
||||
return decimal.Decimal{}, repo.err
|
||||
return 0, repo.err
|
||||
}
|
||||
return repo.sequence, nil
|
||||
}
|
||||
@ -1056,7 +1055,7 @@ func TestEventstore_FilterEvents(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventstore_LatestPosition(t *testing.T) {
|
||||
func TestEventstore_LatestSequence(t *testing.T) {
|
||||
type args struct {
|
||||
query *SearchQueryBuilder
|
||||
}
|
||||
@ -1076,7 +1075,7 @@ func TestEventstore_LatestPosition(t *testing.T) {
|
||||
name: "no events",
|
||||
args: args{
|
||||
query: &SearchQueryBuilder{
|
||||
columns: ColumnsMaxPosition,
|
||||
columns: ColumnsMaxSequence,
|
||||
queries: []*SearchQuery{
|
||||
{
|
||||
builder: &SearchQueryBuilder{},
|
||||
@ -1099,7 +1098,7 @@ func TestEventstore_LatestPosition(t *testing.T) {
|
||||
name: "repo error",
|
||||
args: args{
|
||||
query: &SearchQueryBuilder{
|
||||
columns: ColumnsMaxPosition,
|
||||
columns: ColumnsMaxSequence,
|
||||
queries: []*SearchQuery{
|
||||
{
|
||||
builder: &SearchQueryBuilder{},
|
||||
@ -1122,7 +1121,7 @@ func TestEventstore_LatestPosition(t *testing.T) {
|
||||
name: "found events",
|
||||
args: args{
|
||||
query: &SearchQueryBuilder{
|
||||
columns: ColumnsMaxPosition,
|
||||
columns: ColumnsMaxSequence,
|
||||
queries: []*SearchQuery{
|
||||
{
|
||||
builder: &SearchQueryBuilder{},
|
||||
@ -1148,7 +1147,7 @@ func TestEventstore_LatestPosition(t *testing.T) {
|
||||
querier: tt.fields.repo,
|
||||
}
|
||||
|
||||
_, err := es.LatestPosition(context.Background(), tt.args.query)
|
||||
_, err := es.LatestSequence(context.Background(), tt.args.query)
|
||||
if (err != nil) != tt.res.wantErr {
|
||||
t.Errorf("Eventstore.aggregatesToEvents() error = %v, wantErr %v", err, tt.res.wantErr)
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
"github.com/shopspring/decimal"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
)
|
||||
@ -124,7 +123,7 @@ func (h *FieldHandler) processEvents(ctx context.Context, config *triggerConfig)
|
||||
return additionalIteration, err
|
||||
}
|
||||
// stop execution if currentState.eventTimestamp >= config.maxCreatedAt
|
||||
if !config.maxPosition.IsZero() && currentState.position.GreaterThanOrEqual(config.maxPosition) {
|
||||
if config.maxPosition != 0 && currentState.position >= config.maxPosition {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@ -157,7 +156,7 @@ func (h *FieldHandler) fetchEvents(ctx context.Context, tx *sql.Tx, currentState
|
||||
|
||||
idx, offset := skipPreviouslyReducedEvents(events, currentState)
|
||||
|
||||
if currentState.position.Equal(events[len(events)-1].Position()) {
|
||||
if currentState.position == events[len(events)-1].Position() {
|
||||
offset += currentState.offset
|
||||
}
|
||||
currentState.position = events[len(events)-1].Position()
|
||||
@ -187,9 +186,9 @@ func (h *FieldHandler) fetchEvents(ctx context.Context, tx *sql.Tx, currentState
|
||||
}
|
||||
|
||||
func skipPreviouslyReducedEvents(events []eventstore.Event, currentState *state) (index int, offset uint32) {
|
||||
var position decimal.Decimal
|
||||
var position float64
|
||||
for i, event := range events {
|
||||
if !event.Position().Equal(position) {
|
||||
if event.Position() != position {
|
||||
offset = 0
|
||||
position = event.Position()
|
||||
}
|
||||
|
@ -4,13 +4,13 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"math"
|
||||
"math/rand"
|
||||
"slices"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
@ -379,7 +379,7 @@ func (h *Handler) existingInstances(ctx context.Context) ([]string, error) {
|
||||
|
||||
type triggerConfig struct {
|
||||
awaitRunning bool
|
||||
maxPosition decimal.Decimal
|
||||
maxPosition float64
|
||||
}
|
||||
|
||||
type TriggerOpt func(conf *triggerConfig)
|
||||
@ -390,7 +390,7 @@ func WithAwaitRunning() TriggerOpt {
|
||||
}
|
||||
}
|
||||
|
||||
func WithMaxPosition(position decimal.Decimal) TriggerOpt {
|
||||
func WithMaxPosition(position float64) TriggerOpt {
|
||||
return func(conf *triggerConfig) {
|
||||
conf.maxPosition = position
|
||||
}
|
||||
@ -500,7 +500,7 @@ func (h *Handler) processEvents(ctx context.Context, config *triggerConfig) (add
|
||||
return additionalIteration, err
|
||||
}
|
||||
// stop execution if currentState.eventTimestamp >= config.maxCreatedAt
|
||||
if !config.maxPosition.Equal(decimal.Decimal{}) && currentState.position.GreaterThanOrEqual(config.maxPosition) {
|
||||
if config.maxPosition != 0 && currentState.position >= config.maxPosition {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@ -576,7 +576,7 @@ func (h *Handler) generateStatements(ctx context.Context, tx *sql.Tx, currentSta
|
||||
|
||||
func skipPreviouslyReducedStatements(statements []*Statement, currentState *state) int {
|
||||
for i, statement := range statements {
|
||||
if statement.Position.Equal(currentState.position) &&
|
||||
if statement.Position == currentState.position &&
|
||||
statement.AggregateID == currentState.aggregateID &&
|
||||
statement.AggregateType == currentState.aggregateType &&
|
||||
statement.Sequence == currentState.sequence {
|
||||
@ -609,14 +609,14 @@ func (h *Handler) executeStatement(ctx context.Context, tx *sql.Tx, currentState
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, "SAVEPOINT exec")
|
||||
_, err = tx.Exec("SAVEPOINT exec")
|
||||
if err != nil {
|
||||
h.log().WithError(err).Debug("create savepoint failed")
|
||||
return err
|
||||
}
|
||||
var shouldContinue bool
|
||||
defer func() {
|
||||
_, errSave := tx.ExecContext(ctx, "RELEASE SAVEPOINT exec")
|
||||
_, errSave := tx.Exec("RELEASE SAVEPOINT exec")
|
||||
if err == nil {
|
||||
err = errSave
|
||||
}
|
||||
@ -644,8 +644,9 @@ func (h *Handler) eventQuery(currentState *state) *eventstore.SearchQueryBuilder
|
||||
OrderAsc().
|
||||
InstanceID(currentState.instanceID)
|
||||
|
||||
if currentState.position.GreaterThan(decimal.Decimal{}) {
|
||||
builder = builder.PositionGreaterEqual(currentState.position)
|
||||
if currentState.position > 0 {
|
||||
// decrease position by 10 because builder.PositionAfter filters for position > and we need position >=
|
||||
builder = builder.PositionAfter(math.Float64frombits(math.Float64bits(currentState.position) - 10))
|
||||
if currentState.offset > 0 {
|
||||
builder = builder.Offset(currentState.offset)
|
||||
}
|
||||
|
@ -7,8 +7,6 @@ import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
@ -16,7 +14,7 @@ import (
|
||||
|
||||
type state struct {
|
||||
instanceID string
|
||||
position decimal.Decimal
|
||||
position float64
|
||||
eventTimestamp time.Time
|
||||
aggregateType eventstore.AggregateType
|
||||
aggregateID string
|
||||
@ -47,7 +45,7 @@ func (h *Handler) currentState(ctx context.Context, tx *sql.Tx, config *triggerC
|
||||
aggregateType = new(sql.NullString)
|
||||
sequence = new(sql.NullInt64)
|
||||
timestamp = new(sql.NullTime)
|
||||
position = new(decimal.NullDecimal)
|
||||
position = new(sql.NullFloat64)
|
||||
offset = new(sql.NullInt64)
|
||||
)
|
||||
|
||||
@ -77,7 +75,7 @@ func (h *Handler) currentState(ctx context.Context, tx *sql.Tx, config *triggerC
|
||||
currentState.aggregateType = eventstore.AggregateType(aggregateType.String)
|
||||
currentState.sequence = uint64(sequence.Int64)
|
||||
currentState.eventTimestamp = timestamp.Time
|
||||
currentState.position = position.Decimal
|
||||
currentState.position = position.Float64
|
||||
// psql does not provide unsigned numbers so we work around it
|
||||
currentState.offset = uint32(offset.Int64)
|
||||
return currentState, nil
|
||||
|
@ -11,7 +11,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
"github.com/shopspring/decimal"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/database/mock"
|
||||
@ -167,7 +166,7 @@ func TestHandler_updateLastUpdated(t *testing.T) {
|
||||
updatedState: &state{
|
||||
instanceID: "instance",
|
||||
eventTimestamp: time.Now(),
|
||||
position: decimal.NewFromInt(42),
|
||||
position: 42,
|
||||
},
|
||||
},
|
||||
isErr: func(t *testing.T, err error) {
|
||||
@ -193,7 +192,7 @@ func TestHandler_updateLastUpdated(t *testing.T) {
|
||||
updatedState: &state{
|
||||
instanceID: "instance",
|
||||
eventTimestamp: time.Now(),
|
||||
position: decimal.NewFromInt(42),
|
||||
position: 42,
|
||||
},
|
||||
},
|
||||
isErr: func(t *testing.T, err error) {
|
||||
@ -218,7 +217,7 @@ func TestHandler_updateLastUpdated(t *testing.T) {
|
||||
eventstore.AggregateType("aggregate type"),
|
||||
uint64(42),
|
||||
mock.AnyType[time.Time]{},
|
||||
decimal.NewFromInt(42),
|
||||
float64(42),
|
||||
uint32(0),
|
||||
),
|
||||
mock.WithExecRowsAffected(1),
|
||||
@ -229,7 +228,7 @@ func TestHandler_updateLastUpdated(t *testing.T) {
|
||||
updatedState: &state{
|
||||
instanceID: "instance",
|
||||
eventTimestamp: time.Now(),
|
||||
position: decimal.NewFromInt(42),
|
||||
position: 42,
|
||||
aggregateType: "aggregate type",
|
||||
aggregateID: "aggregate id",
|
||||
sequence: 42,
|
||||
@ -398,7 +397,7 @@ func TestHandler_currentState(t *testing.T) {
|
||||
"aggregate type",
|
||||
int64(42),
|
||||
testTime,
|
||||
decimal.NewFromInt(42).String(),
|
||||
float64(42),
|
||||
uint16(10),
|
||||
},
|
||||
},
|
||||
@ -413,7 +412,7 @@ func TestHandler_currentState(t *testing.T) {
|
||||
currentState: &state{
|
||||
instanceID: "instance",
|
||||
eventTimestamp: testTime,
|
||||
position: decimal.NewFromInt(42),
|
||||
position: 42,
|
||||
aggregateType: "aggregate type",
|
||||
aggregateID: "aggregate id",
|
||||
sequence: 42,
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/zitadel/logging"
|
||||
"golang.org/x/exp/constraints"
|
||||
|
||||
@ -84,7 +83,7 @@ type Statement struct {
|
||||
AggregateType eventstore.AggregateType
|
||||
AggregateID string
|
||||
Sequence uint64
|
||||
Position decimal.Decimal
|
||||
Position float64
|
||||
CreationDate time.Time
|
||||
InstanceID string
|
||||
|
||||
|
@ -2,16 +2,13 @@ package eventstore_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cockroachdb/cockroach-go/v2/testserver"
|
||||
pgxdecimal "github.com/jackc/pgx-shopspring-decimal"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/jackc/pgx/v5/stdlib"
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/cmd/initialise"
|
||||
@ -42,19 +39,10 @@ func TestMain(m *testing.M) {
|
||||
testCRDBClient = &database.DB{
|
||||
Database: new(testDB),
|
||||
}
|
||||
config, err := pgxpool.ParseConfig(ts.PGURL().String())
|
||||
testCRDBClient.DB, err = sql.Open("postgres", ts.PGURL().String())
|
||||
if err != nil {
|
||||
logging.WithFields("error", err).Fatal("unable to parse db config")
|
||||
logging.WithFields("error", err).Fatal("unable to connect to db")
|
||||
}
|
||||
config.AfterConnect = func(ctx context.Context, conn *pgx.Conn) error {
|
||||
pgxdecimal.Register(conn.TypeMap())
|
||||
return nil
|
||||
}
|
||||
pool, err := pgxpool.NewWithConfig(context.Background(), config)
|
||||
if err != nil {
|
||||
logging.WithFields("error", err).Fatal("unable to create db pool")
|
||||
}
|
||||
testCRDBClient.DB = stdlib.OpenDBFromPool(pool)
|
||||
if err = testCRDBClient.Ping(); err != nil {
|
||||
logging.WithFields("error", err).Fatal("unable to ping db")
|
||||
}
|
||||
@ -115,19 +103,10 @@ func initDB(db *database.DB) error {
|
||||
}
|
||||
|
||||
func connectLocalhost() (*database.DB, error) {
|
||||
config, err := pgxpool.ParseConfig("postgresql://root@localhost:26257/defaultdb?sslmode=disable")
|
||||
client, err := sql.Open("pgx", "postgresql://root@localhost:26257/defaultdb?sslmode=disable")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.AfterConnect = func(ctx context.Context, conn *pgx.Conn) error {
|
||||
pgxdecimal.Register(conn.TypeMap())
|
||||
return nil
|
||||
}
|
||||
pool, err := pgxpool.NewWithConfig(context.Background(), config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client := stdlib.OpenDBFromPool(pool)
|
||||
if err = client.Ping(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -1,23 +1,19 @@
|
||||
package eventstore
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
import "time"
|
||||
|
||||
// ReadModel is the minimum representation of a read model.
|
||||
// It implements a basic reducer
|
||||
// it might be saved in a database or in memory
|
||||
type ReadModel struct {
|
||||
AggregateID string `json:"-"`
|
||||
ProcessedSequence uint64 `json:"-"`
|
||||
CreationDate time.Time `json:"-"`
|
||||
ChangeDate time.Time `json:"-"`
|
||||
Events []Event `json:"-"`
|
||||
ResourceOwner string `json:"-"`
|
||||
InstanceID string `json:"-"`
|
||||
Position decimal.Decimal `json:"-"`
|
||||
AggregateID string `json:"-"`
|
||||
ProcessedSequence uint64 `json:"-"`
|
||||
CreationDate time.Time `json:"-"`
|
||||
ChangeDate time.Time `json:"-"`
|
||||
Events []Event `json:"-"`
|
||||
ResourceOwner string `json:"-"`
|
||||
InstanceID string `json:"-"`
|
||||
Position float64 `json:"-"`
|
||||
}
|
||||
|
||||
// AppendEvents adds all the events to the read model.
|
||||
|
@ -5,8 +5,6 @@ import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
)
|
||||
|
||||
@ -20,7 +18,7 @@ type Event struct {
|
||||
// Seq is the sequence of the event
|
||||
Seq uint64
|
||||
// Pos is the global sequence of the event multiple events can have the same sequence
|
||||
Pos decimal.Decimal
|
||||
Pos float64
|
||||
|
||||
//CreationDate is the time the event is created
|
||||
// it's used for human readability.
|
||||
@ -93,7 +91,7 @@ func (e *Event) Sequence() uint64 {
|
||||
}
|
||||
|
||||
// Position implements [eventstore.Event]
|
||||
func (e *Event) Position() decimal.Decimal {
|
||||
func (e *Event) Position() float64 {
|
||||
return e.Pos
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,6 @@ import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
decimal "github.com/shopspring/decimal"
|
||||
eventstore "github.com/zitadel/zitadel/internal/eventstore"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
@ -84,19 +83,19 @@ func (mr *MockQuerierMockRecorder) InstanceIDs(arg0, arg1 any) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstanceIDs", reflect.TypeOf((*MockQuerier)(nil).InstanceIDs), arg0, arg1)
|
||||
}
|
||||
|
||||
// LatestPosition mocks base method.
|
||||
func (m *MockQuerier) LatestPosition(arg0 context.Context, arg1 *eventstore.SearchQueryBuilder) (decimal.Decimal, error) {
|
||||
// LatestSequence mocks base method.
|
||||
func (m *MockQuerier) LatestSequence(arg0 context.Context, arg1 *eventstore.SearchQueryBuilder) (float64, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "LatestPosition", arg0, arg1)
|
||||
ret0, _ := ret[0].(decimal.Decimal)
|
||||
ret := m.ctrl.Call(m, "LatestSequence", arg0, arg1)
|
||||
ret0, _ := ret[0].(float64)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// LatestPosition indicates an expected call of LatestPosition.
|
||||
func (mr *MockQuerierMockRecorder) LatestPosition(arg0, arg1 any) *gomock.Call {
|
||||
// LatestSequence indicates an expected call of LatestSequence.
|
||||
func (mr *MockQuerierMockRecorder) LatestSequence(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LatestPosition", reflect.TypeOf((*MockQuerier)(nil).LatestPosition), arg0, arg1)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LatestSequence", reflect.TypeOf((*MockQuerier)(nil).LatestSequence), arg0, arg1)
|
||||
}
|
||||
|
||||
// MockPusher is a mock of Pusher interface.
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/mock/gomock"
|
||||
|
||||
@ -187,8 +186,8 @@ func (e *mockEvent) Sequence() uint64 {
|
||||
return e.sequence
|
||||
}
|
||||
|
||||
func (e *mockEvent) Position() decimal.Decimal {
|
||||
return decimal.Decimal{}
|
||||
func (e *mockEvent) Position() float64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (e *mockEvent) CreatedAt() time.Time {
|
||||
|
@ -55,8 +55,6 @@ const (
|
||||
//OperationNotIn checks if a stored value does not match one of the passed value list
|
||||
OperationNotIn
|
||||
|
||||
OperationGreaterEqual
|
||||
|
||||
operationCount
|
||||
)
|
||||
|
||||
@ -234,10 +232,10 @@ func instanceIDsFilter(builder *eventstore.SearchQueryBuilder, query *SearchQuer
|
||||
}
|
||||
|
||||
func positionAfterFilter(builder *eventstore.SearchQueryBuilder, query *SearchQuery) *Filter {
|
||||
if builder.GetPositionAfter().IsZero() {
|
||||
if builder.GetPositionAfter() == 0 {
|
||||
return nil
|
||||
}
|
||||
query.Position = NewFilter(FieldPosition, builder.GetPositionAfter(), OperationGreaterEqual)
|
||||
query.Position = NewFilter(FieldPosition, builder.GetPositionAfter(), OperationGreater)
|
||||
return query.Position
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,6 @@ import (
|
||||
|
||||
"github.com/cockroachdb/cockroach-go/v2/crdb"
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
@ -266,11 +265,11 @@ func (crdb *CRDB) FilterToReducer(ctx context.Context, searchQuery *eventstore.S
|
||||
return err
|
||||
}
|
||||
|
||||
// LatestPosition returns the latest position found by the search query
|
||||
func (db *CRDB) LatestPosition(ctx context.Context, searchQuery *eventstore.SearchQueryBuilder) (decimal.Decimal, error) {
|
||||
var position decimal.Decimal
|
||||
// LatestSequence returns the latest sequence found by the search query
|
||||
func (db *CRDB) LatestSequence(ctx context.Context, searchQuery *eventstore.SearchQueryBuilder) (float64, error) {
|
||||
var position sql.NullFloat64
|
||||
err := query(ctx, db, searchQuery, &position, false)
|
||||
return position, err
|
||||
return position.Float64, err
|
||||
}
|
||||
|
||||
// InstanceIDs returns the instance ids found by the search query
|
||||
@ -337,7 +336,7 @@ func (db *CRDB) eventQuery(useV1 bool) string {
|
||||
" FROM eventstore.events2"
|
||||
}
|
||||
|
||||
func (db *CRDB) maxPositionQuery(useV1 bool) string {
|
||||
func (db *CRDB) maxSequenceQuery(useV1 bool) string {
|
||||
if useV1 {
|
||||
return `SELECT event_sequence FROM eventstore.events`
|
||||
}
|
||||
@ -415,8 +414,6 @@ func (db *CRDB) operation(operation repository.Operation) string {
|
||||
return "="
|
||||
case repository.OperationGreater:
|
||||
return ">"
|
||||
case repository.OperationGreaterEqual:
|
||||
return ">="
|
||||
case repository.OperationLess:
|
||||
return "<"
|
||||
case repository.OperationJSONContains:
|
||||
|
@ -4,8 +4,6 @@ import (
|
||||
"database/sql"
|
||||
"testing"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/repository"
|
||||
)
|
||||
@ -314,7 +312,7 @@ func generateEvent(t *testing.T, aggregateID string, opts ...func(*repository.Ev
|
||||
ResourceOwner: sql.NullString{String: "ro", Valid: true},
|
||||
Typ: "test.created",
|
||||
Version: "v1",
|
||||
Pos: decimal.NewFromInt(42),
|
||||
Pos: 42,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/call"
|
||||
@ -26,7 +25,7 @@ type querier interface {
|
||||
conditionFormat(repository.Operation) string
|
||||
placeholder(query string) string
|
||||
eventQuery(useV1 bool) string
|
||||
maxPositionQuery(useV1 bool) string
|
||||
maxSequenceQuery(useV1 bool) string
|
||||
instanceIDsQuery(useV1 bool) string
|
||||
db() *database.DB
|
||||
orderByEventSequence(desc, shouldOrderBySequence, useV1 bool) string
|
||||
@ -75,7 +74,7 @@ func query(ctx context.Context, criteria querier, searchQuery *eventstore.Search
|
||||
|
||||
// instead of using the max function of the database (which doesn't work for postgres)
|
||||
// we select the most recent row
|
||||
if q.Columns == eventstore.ColumnsMaxPosition {
|
||||
if q.Columns == eventstore.ColumnsMaxSequence {
|
||||
q.Limit = 1
|
||||
q.Desc = true
|
||||
}
|
||||
@ -92,7 +91,7 @@ func query(ctx context.Context, criteria querier, searchQuery *eventstore.Search
|
||||
|
||||
switch q.Columns {
|
||||
case eventstore.ColumnsEvent,
|
||||
eventstore.ColumnsMaxPosition:
|
||||
eventstore.ColumnsMaxSequence:
|
||||
query += criteria.orderByEventSequence(q.Desc, shouldOrderBySequence, useV1)
|
||||
}
|
||||
|
||||
@ -136,8 +135,8 @@ func query(ctx context.Context, criteria querier, searchQuery *eventstore.Search
|
||||
|
||||
func prepareColumns(criteria querier, columns eventstore.Columns, useV1 bool) (string, func(s scan, dest interface{}) error) {
|
||||
switch columns {
|
||||
case eventstore.ColumnsMaxPosition:
|
||||
return criteria.maxPositionQuery(useV1), maxPositionScanner
|
||||
case eventstore.ColumnsMaxSequence:
|
||||
return criteria.maxSequenceQuery(useV1), maxSequenceScanner
|
||||
case eventstore.ColumnsInstanceIDs:
|
||||
return criteria.instanceIDsQuery(useV1), instanceIDsScanner
|
||||
case eventstore.ColumnsEvent:
|
||||
@ -155,15 +154,13 @@ func prepareTimeTravel(ctx context.Context, criteria querier, allow bool) string
|
||||
return criteria.Timetravel(took)
|
||||
}
|
||||
|
||||
func maxPositionScanner(row scan, dest interface{}) (err error) {
|
||||
position, ok := dest.(*decimal.Decimal)
|
||||
func maxSequenceScanner(row scan, dest interface{}) (err error) {
|
||||
position, ok := dest.(*sql.NullFloat64)
|
||||
if !ok {
|
||||
return zerrors.ThrowInvalidArgumentf(nil, "SQL-NBjA9", "type must be decimal.Decimal got: %T", dest)
|
||||
return zerrors.ThrowInvalidArgumentf(nil, "SQL-NBjA9", "type must be sql.NullInt64 got: %T", dest)
|
||||
}
|
||||
var res decimal.NullDecimal
|
||||
err = row(&res)
|
||||
err = row(position)
|
||||
if err == nil || errors.Is(err, sql.ErrNoRows) {
|
||||
*position = res.Decimal
|
||||
return nil
|
||||
}
|
||||
return zerrors.ThrowInternal(err, "SQL-bN5xg", "something went wrong")
|
||||
@ -192,7 +189,7 @@ func eventsScanner(useV1 bool) func(scanner scan, dest interface{}) (err error)
|
||||
return zerrors.ThrowInvalidArgumentf(nil, "SQL-4GP6F", "events scanner: invalid type %T", dest)
|
||||
}
|
||||
event := new(repository.Event)
|
||||
position := new(decimal.NullDecimal)
|
||||
position := new(sql.NullFloat64)
|
||||
|
||||
if useV1 {
|
||||
err = scanner(
|
||||
@ -229,7 +226,7 @@ func eventsScanner(useV1 bool) func(scanner scan, dest interface{}) (err error)
|
||||
logging.New().WithError(err).Warn("unable to scan row")
|
||||
return zerrors.ThrowInternal(err, "SQL-M0dsf", "unable to scan row")
|
||||
}
|
||||
event.Pos = position.Decimal
|
||||
event.Pos = position.Float64
|
||||
return reduce(event)
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
@ -110,36 +109,36 @@ func Test_prepareColumns(t *testing.T) {
|
||||
{
|
||||
name: "max column",
|
||||
args: args{
|
||||
columns: eventstore.ColumnsMaxPosition,
|
||||
dest: new(decimal.Decimal),
|
||||
columns: eventstore.ColumnsMaxSequence,
|
||||
dest: new(sql.NullFloat64),
|
||||
useV1: true,
|
||||
},
|
||||
res: res{
|
||||
query: `SELECT event_sequence FROM eventstore.events`,
|
||||
expected: decimal.NewFromInt(42),
|
||||
expected: sql.NullFloat64{Float64: 43, Valid: true},
|
||||
},
|
||||
fields: fields{
|
||||
dbRow: []interface{}{decimal.NewNullDecimal(decimal.NewFromInt(42))},
|
||||
dbRow: []interface{}{sql.NullFloat64{Float64: 43, Valid: true}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "max column v2",
|
||||
args: args{
|
||||
columns: eventstore.ColumnsMaxPosition,
|
||||
dest: new(decimal.Decimal),
|
||||
columns: eventstore.ColumnsMaxSequence,
|
||||
dest: new(sql.NullFloat64),
|
||||
},
|
||||
res: res{
|
||||
query: `SELECT "position" FROM eventstore.events2`,
|
||||
expected: decimal.NewFromInt(42),
|
||||
expected: sql.NullFloat64{Float64: 43, Valid: true},
|
||||
},
|
||||
fields: fields{
|
||||
dbRow: []interface{}{decimal.NewNullDecimal(decimal.NewFromInt(42))},
|
||||
dbRow: []interface{}{sql.NullFloat64{Float64: 43, Valid: true}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "max sequence wrong dest type",
|
||||
args: args{
|
||||
columns: eventstore.ColumnsMaxPosition,
|
||||
columns: eventstore.ColumnsMaxSequence,
|
||||
dest: new(uint64),
|
||||
},
|
||||
res: res{
|
||||
@ -179,11 +178,11 @@ func Test_prepareColumns(t *testing.T) {
|
||||
res: res{
|
||||
query: `SELECT created_at, event_type, "sequence", "position", payload, creator, "owner", instance_id, aggregate_type, aggregate_id, revision FROM eventstore.events2`,
|
||||
expected: []eventstore.Event{
|
||||
&repository.Event{AggregateID: "hodor", AggregateType: "user", Seq: 5, Pos: decimal.NewFromInt(42), Data: nil, Version: "v1"},
|
||||
&repository.Event{AggregateID: "hodor", AggregateType: "user", Seq: 5, Pos: 42, Data: nil, Version: "v1"},
|
||||
},
|
||||
},
|
||||
fields: fields{
|
||||
dbRow: []interface{}{time.Time{}, eventstore.EventType(""), uint64(5), decimal.NewNullDecimal(decimal.NewFromInt(42)), sql.RawBytes(nil), "", sql.NullString{}, "", eventstore.AggregateType("user"), "hodor", uint8(1)},
|
||||
dbRow: []interface{}{time.Time{}, eventstore.EventType(""), uint64(5), sql.NullFloat64{Float64: 42, Valid: true}, sql.RawBytes(nil), "", sql.NullString{}, "", eventstore.AggregateType("user"), "hodor", uint8(1)},
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -198,11 +197,11 @@ func Test_prepareColumns(t *testing.T) {
|
||||
res: res{
|
||||
query: `SELECT created_at, event_type, "sequence", "position", payload, creator, "owner", instance_id, aggregate_type, aggregate_id, revision FROM eventstore.events2`,
|
||||
expected: []eventstore.Event{
|
||||
&repository.Event{AggregateID: "hodor", AggregateType: "user", Seq: 5, Pos: decimal.Decimal{}, Data: nil, Version: "v1"},
|
||||
&repository.Event{AggregateID: "hodor", AggregateType: "user", Seq: 5, Pos: 0, Data: nil, Version: "v1"},
|
||||
},
|
||||
},
|
||||
fields: fields{
|
||||
dbRow: []interface{}{time.Time{}, eventstore.EventType(""), uint64(5), decimal.NullDecimal{}, sql.RawBytes(nil), "", sql.NullString{}, "", eventstore.AggregateType("user"), "hodor", uint8(1)},
|
||||
dbRow: []interface{}{time.Time{}, eventstore.EventType(""), uint64(5), sql.NullFloat64{Float64: 0, Valid: false}, sql.RawBytes(nil), "", sql.NullString{}, "", eventstore.AggregateType("user"), "hodor", uint8(1)},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -5,8 +5,6 @@ import (
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
@ -25,7 +23,7 @@ type SearchQueryBuilder struct {
|
||||
queries []*SearchQuery
|
||||
tx *sql.Tx
|
||||
allowTimeTravel bool
|
||||
positionGreaterEqual decimal.Decimal
|
||||
positionAfter float64
|
||||
awaitOpenTransactions bool
|
||||
creationDateAfter time.Time
|
||||
creationDateBefore time.Time
|
||||
@ -76,8 +74,8 @@ func (b *SearchQueryBuilder) GetAllowTimeTravel() bool {
|
||||
return b.allowTimeTravel
|
||||
}
|
||||
|
||||
func (b SearchQueryBuilder) GetPositionAfter() decimal.Decimal {
|
||||
return b.positionGreaterEqual
|
||||
func (b SearchQueryBuilder) GetPositionAfter() float64 {
|
||||
return b.positionAfter
|
||||
}
|
||||
|
||||
func (b SearchQueryBuilder) GetAwaitOpenTransactions() bool {
|
||||
@ -133,8 +131,8 @@ type Columns int8
|
||||
const (
|
||||
//ColumnsEvent represents all fields of an event
|
||||
ColumnsEvent = iota + 1
|
||||
// ColumnsMaxPosition represents the latest sequence of the filtered events
|
||||
ColumnsMaxPosition
|
||||
// ColumnsMaxSequence represents the latest sequence of the filtered events
|
||||
ColumnsMaxSequence
|
||||
// ColumnsInstanceIDs represents the instance ids of the filtered events
|
||||
ColumnsInstanceIDs
|
||||
|
||||
@ -269,8 +267,8 @@ func (builder *SearchQueryBuilder) AllowTimeTravel() *SearchQueryBuilder {
|
||||
}
|
||||
|
||||
// PositionAfter filters for events which happened after the specified time
|
||||
func (builder *SearchQueryBuilder) PositionGreaterEqual(position decimal.Decimal) *SearchQueryBuilder {
|
||||
builder.positionGreaterEqual = position
|
||||
func (builder *SearchQueryBuilder) PositionAfter(position float64) *SearchQueryBuilder {
|
||||
builder.positionAfter = position
|
||||
return builder
|
||||
}
|
||||
|
||||
|
@ -116,10 +116,10 @@ func TestSearchQuerybuilderSetters(t *testing.T) {
|
||||
{
|
||||
name: "set columns",
|
||||
args: args{
|
||||
setters: []func(*SearchQueryBuilder) *SearchQueryBuilder{testSetColumns(ColumnsMaxPosition)},
|
||||
setters: []func(*SearchQueryBuilder) *SearchQueryBuilder{testSetColumns(ColumnsMaxSequence)},
|
||||
},
|
||||
res: &SearchQueryBuilder{
|
||||
columns: ColumnsMaxPosition,
|
||||
columns: ColumnsMaxSequence,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -5,8 +5,6 @@ import (
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
@ -22,7 +20,7 @@ var _ eventstore.Event = (*Event)(nil)
|
||||
type Event struct {
|
||||
ID string
|
||||
Seq uint64
|
||||
Pos decimal.Decimal
|
||||
Pos float64
|
||||
CreationDate time.Time
|
||||
Typ eventstore.EventType
|
||||
PreviousSequence uint64
|
||||
@ -82,7 +80,7 @@ func (e *Event) Sequence() uint64 {
|
||||
}
|
||||
|
||||
// Position implements [eventstore.Event]
|
||||
func (e *Event) Position() decimal.Decimal {
|
||||
func (e *Event) Position() float64 {
|
||||
return e.Pos
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
@ -22,7 +21,7 @@ type event struct {
|
||||
typ eventstore.EventType
|
||||
createdAt time.Time
|
||||
sequence uint64
|
||||
position decimal.Decimal
|
||||
position float64
|
||||
payload Payload
|
||||
}
|
||||
|
||||
@ -85,8 +84,8 @@ func (e *event) Sequence() uint64 {
|
||||
return e.sequence
|
||||
}
|
||||
|
||||
// Position implements [eventstore.Event]
|
||||
func (e *event) Position() decimal.Decimal {
|
||||
// Sequence implements [eventstore.Event]
|
||||
func (e *event) Position() float64 {
|
||||
return e.position
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"github.com/zitadel/zitadel/pkg/grpc/authn"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/management"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user"
|
||||
user_v2 "github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
func (i *Instance) CreateOIDCClient(ctx context.Context, redirectURI, logoutRedirectURI, projectID string, appType app.OIDCAppType, authMethod app.OIDCAuthMethodType, devMode bool, grantTypes ...app.OIDCGrantType) (*management.AddOIDCAppResponse, error) {
|
||||
@ -107,6 +108,20 @@ func (i *Instance) CreateOIDCInactivateClient(ctx context.Context, redirectURI,
|
||||
return client, err
|
||||
}
|
||||
|
||||
func (i *Instance) CreateOIDCInactivateProjectClient(ctx context.Context, redirectURI, logoutRedirectURI, projectID string) (*management.AddOIDCAppResponse, error) {
|
||||
client, err := i.CreateOIDCNativeClient(ctx, redirectURI, logoutRedirectURI, projectID, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = i.Client.Mgmt.DeactivateProject(ctx, &management.DeactivateProjectRequest{
|
||||
Id: projectID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client, err
|
||||
}
|
||||
|
||||
func (i *Instance) CreateOIDCImplicitFlowClient(ctx context.Context, redirectURI string) (*management.AddOIDCAppResponse, error) {
|
||||
project, err := i.Client.Mgmt.AddProject(ctx, &management.AddProjectRequest{
|
||||
Name: fmt.Sprintf("project-%d", time.Now().UnixNano()),
|
||||
@ -341,6 +356,31 @@ func (i *Instance) CreateOIDCCredentialsClient(ctx context.Context) (machine *ma
|
||||
return machine, name, secret.GetClientId(), secret.GetClientSecret(), nil
|
||||
}
|
||||
|
||||
func (i *Instance) CreateOIDCCredentialsClientInactive(ctx context.Context) (machine *management.AddMachineUserResponse, name, clientID, clientSecret string, err error) {
|
||||
name = gofakeit.Username()
|
||||
machine, err = i.Client.Mgmt.AddMachineUser(ctx, &management.AddMachineUserRequest{
|
||||
Name: name,
|
||||
UserName: name,
|
||||
AccessTokenType: user.AccessTokenType_ACCESS_TOKEN_TYPE_JWT,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
secret, err := i.Client.Mgmt.GenerateMachineSecret(ctx, &management.GenerateMachineSecretRequest{
|
||||
UserId: machine.GetUserId(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
_, err = i.Client.UserV2.DeactivateUser(ctx, &user_v2.DeactivateUserRequest{
|
||||
UserId: machine.GetUserId(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
return machine, name, secret.GetClientId(), secret.GetClientSecret(), nil
|
||||
}
|
||||
|
||||
func (i *Instance) CreateOIDCJWTProfileClient(ctx context.Context) (machine *management.AddMachineUserResponse, name string, keyData []byte, err error) {
|
||||
name = gofakeit.Username()
|
||||
machine, err = i.Client.Mgmt.AddMachineUser(ctx, &management.AddMachineUserRequest{
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
@ -141,7 +140,7 @@ func (q *Queries) accessTokenByOIDCSessionAndTokenID(ctx context.Context, oidcSe
|
||||
|
||||
// checkSessionNotTerminatedAfter checks if a [session.TerminateType] event (or user events leading to a session termination)
|
||||
// occurred after a certain time and will return an error if so.
|
||||
func (q *Queries) checkSessionNotTerminatedAfter(ctx context.Context, sessionID, userID string, position decimal.Decimal, fingerprintID string) (err error) {
|
||||
func (q *Queries) checkSessionNotTerminatedAfter(ctx context.Context, sessionID, userID string, position float64, fingerprintID string) (err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
@ -163,7 +162,7 @@ func (q *Queries) checkSessionNotTerminatedAfter(ctx context.Context, sessionID,
|
||||
}
|
||||
|
||||
type sessionTerminatedModel struct {
|
||||
position decimal.Decimal
|
||||
position float64
|
||||
sessionID string
|
||||
userID string
|
||||
fingerPrintID string
|
||||
@ -183,7 +182,7 @@ func (s *sessionTerminatedModel) AppendEvents(events ...eventstore.Event) {
|
||||
|
||||
func (s *sessionTerminatedModel) Query() *eventstore.SearchQueryBuilder {
|
||||
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
PositionGreaterEqual(s.position).
|
||||
PositionAfter(s.position).
|
||||
AddQuery().
|
||||
AggregateTypes(session.AggregateType).
|
||||
AggregateIDs(s.sessionID).
|
||||
|
@ -256,7 +256,7 @@ func (q *Queries) AppByProjectAndAppID(ctx context.Context, shouldTriggerBulk bo
|
||||
traceSpan.EndWithError(err)
|
||||
}
|
||||
|
||||
stmt, scan := prepareAppQuery(ctx, q.client)
|
||||
stmt, scan := prepareAppQuery(ctx, q.client, false)
|
||||
eq := sq.Eq{
|
||||
AppColumnID.identifier(): appID,
|
||||
AppColumnProjectID.identifier(): projectID,
|
||||
@ -274,15 +274,20 @@ func (q *Queries) AppByProjectAndAppID(ctx context.Context, shouldTriggerBulk bo
|
||||
return app, err
|
||||
}
|
||||
|
||||
func (q *Queries) AppByID(ctx context.Context, appID string) (app *App, err error) {
|
||||
func (q *Queries) AppByID(ctx context.Context, appID string, activeOnly bool) (app *App, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
stmt, scan := prepareAppQuery(ctx, q.client)
|
||||
stmt, scan := prepareAppQuery(ctx, q.client, activeOnly)
|
||||
eq := sq.Eq{
|
||||
AppColumnID.identifier(): appID,
|
||||
AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
|
||||
}
|
||||
if activeOnly {
|
||||
eq[AppColumnState.identifier()] = domain.AppStateActive
|
||||
eq[ProjectColumnState.identifier()] = domain.ProjectStateActive
|
||||
eq[OrgColumnState.identifier()] = domain.OrgStateActive
|
||||
}
|
||||
query, args, err := stmt.Where(eq).ToSql()
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-immt9", "Errors.Query.SQLStatement")
|
||||
@ -295,7 +300,7 @@ func (q *Queries) AppByID(ctx context.Context, appID string) (app *App, err erro
|
||||
return app, err
|
||||
}
|
||||
|
||||
func (q *Queries) AppBySAMLEntityID(ctx context.Context, entityID string) (app *App, err error) {
|
||||
func (q *Queries) ActiveAppBySAMLEntityID(ctx context.Context, entityID string) (app *App, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
@ -303,6 +308,9 @@ func (q *Queries) AppBySAMLEntityID(ctx context.Context, entityID string) (app *
|
||||
eq := sq.Eq{
|
||||
AppSAMLConfigColumnEntityID.identifier(): entityID,
|
||||
AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
|
||||
AppColumnState.identifier(): domain.AppStateActive,
|
||||
ProjectColumnState.identifier(): domain.ProjectStateActive,
|
||||
OrgColumnState.identifier(): domain.OrgStateActive,
|
||||
}
|
||||
query, args, err := stmt.Where(eq).ToSql()
|
||||
if err != nil {
|
||||
@ -413,8 +421,13 @@ func (q *Queries) AppByClientID(ctx context.Context, clientID string) (app *App,
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
stmt, scan := prepareAppQuery(ctx, q.client)
|
||||
eq := sq.Eq{AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID()}
|
||||
stmt, scan := prepareAppQuery(ctx, q.client, true)
|
||||
eq := sq.Eq{
|
||||
AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
|
||||
AppColumnState.identifier(): domain.AppStateActive,
|
||||
ProjectColumnState.identifier(): domain.ProjectStateActive,
|
||||
OrgColumnState.identifier(): domain.OrgStateActive,
|
||||
}
|
||||
query, args, err := stmt.Where(sq.And{
|
||||
eq,
|
||||
sq.Or{
|
||||
@ -491,107 +504,121 @@ func NewAppProjectIDSearchQuery(id string) (SearchQuery, error) {
|
||||
return NewTextQuery(AppColumnProjectID, id, TextEquals)
|
||||
}
|
||||
|
||||
func prepareAppQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
|
||||
return sq.Select(
|
||||
AppColumnID.identifier(),
|
||||
AppColumnName.identifier(),
|
||||
AppColumnProjectID.identifier(),
|
||||
AppColumnCreationDate.identifier(),
|
||||
AppColumnChangeDate.identifier(),
|
||||
AppColumnResourceOwner.identifier(),
|
||||
AppColumnState.identifier(),
|
||||
AppColumnSequence.identifier(),
|
||||
func prepareAppQuery(ctx context.Context, db prepareDatabase, activeOnly bool) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
|
||||
query := sq.Select(
|
||||
AppColumnID.identifier(),
|
||||
AppColumnName.identifier(),
|
||||
AppColumnProjectID.identifier(),
|
||||
AppColumnCreationDate.identifier(),
|
||||
AppColumnChangeDate.identifier(),
|
||||
AppColumnResourceOwner.identifier(),
|
||||
AppColumnState.identifier(),
|
||||
AppColumnSequence.identifier(),
|
||||
|
||||
AppAPIConfigColumnAppID.identifier(),
|
||||
AppAPIConfigColumnClientID.identifier(),
|
||||
AppAPIConfigColumnAuthMethod.identifier(),
|
||||
AppAPIConfigColumnAppID.identifier(),
|
||||
AppAPIConfigColumnClientID.identifier(),
|
||||
AppAPIConfigColumnAuthMethod.identifier(),
|
||||
|
||||
AppOIDCConfigColumnAppID.identifier(),
|
||||
AppOIDCConfigColumnVersion.identifier(),
|
||||
AppOIDCConfigColumnClientID.identifier(),
|
||||
AppOIDCConfigColumnRedirectUris.identifier(),
|
||||
AppOIDCConfigColumnResponseTypes.identifier(),
|
||||
AppOIDCConfigColumnGrantTypes.identifier(),
|
||||
AppOIDCConfigColumnApplicationType.identifier(),
|
||||
AppOIDCConfigColumnAuthMethodType.identifier(),
|
||||
AppOIDCConfigColumnPostLogoutRedirectUris.identifier(),
|
||||
AppOIDCConfigColumnDevMode.identifier(),
|
||||
AppOIDCConfigColumnAccessTokenType.identifier(),
|
||||
AppOIDCConfigColumnAccessTokenRoleAssertion.identifier(),
|
||||
AppOIDCConfigColumnIDTokenRoleAssertion.identifier(),
|
||||
AppOIDCConfigColumnIDTokenUserinfoAssertion.identifier(),
|
||||
AppOIDCConfigColumnClockSkew.identifier(),
|
||||
AppOIDCConfigColumnAdditionalOrigins.identifier(),
|
||||
AppOIDCConfigColumnSkipNativeAppSuccessPage.identifier(),
|
||||
AppOIDCConfigColumnAppID.identifier(),
|
||||
AppOIDCConfigColumnVersion.identifier(),
|
||||
AppOIDCConfigColumnClientID.identifier(),
|
||||
AppOIDCConfigColumnRedirectUris.identifier(),
|
||||
AppOIDCConfigColumnResponseTypes.identifier(),
|
||||
AppOIDCConfigColumnGrantTypes.identifier(),
|
||||
AppOIDCConfigColumnApplicationType.identifier(),
|
||||
AppOIDCConfigColumnAuthMethodType.identifier(),
|
||||
AppOIDCConfigColumnPostLogoutRedirectUris.identifier(),
|
||||
AppOIDCConfigColumnDevMode.identifier(),
|
||||
AppOIDCConfigColumnAccessTokenType.identifier(),
|
||||
AppOIDCConfigColumnAccessTokenRoleAssertion.identifier(),
|
||||
AppOIDCConfigColumnIDTokenRoleAssertion.identifier(),
|
||||
AppOIDCConfigColumnIDTokenUserinfoAssertion.identifier(),
|
||||
AppOIDCConfigColumnClockSkew.identifier(),
|
||||
AppOIDCConfigColumnAdditionalOrigins.identifier(),
|
||||
AppOIDCConfigColumnSkipNativeAppSuccessPage.identifier(),
|
||||
|
||||
AppSAMLConfigColumnAppID.identifier(),
|
||||
AppSAMLConfigColumnEntityID.identifier(),
|
||||
AppSAMLConfigColumnMetadata.identifier(),
|
||||
AppSAMLConfigColumnMetadataURL.identifier(),
|
||||
).From(appsTable.identifier()).
|
||||
AppSAMLConfigColumnAppID.identifier(),
|
||||
AppSAMLConfigColumnEntityID.identifier(),
|
||||
AppSAMLConfigColumnMetadata.identifier(),
|
||||
AppSAMLConfigColumnMetadataURL.identifier(),
|
||||
).From(appsTable.identifier()).
|
||||
PlaceholderFormat(sq.Dollar)
|
||||
|
||||
if activeOnly {
|
||||
return query.
|
||||
LeftJoin(join(AppAPIConfigColumnAppID, AppColumnID)).
|
||||
LeftJoin(join(AppOIDCConfigColumnAppID, AppColumnID)).
|
||||
LeftJoin(join(AppSAMLConfigColumnAppID, AppColumnID)).
|
||||
LeftJoin(join(ProjectColumnID, AppColumnProjectID)).
|
||||
LeftJoin(join(OrgColumnID, AppColumnResourceOwner) + db.Timetravel(call.Took(ctx))),
|
||||
scanApp
|
||||
}
|
||||
return query.
|
||||
LeftJoin(join(AppAPIConfigColumnAppID, AppColumnID)).
|
||||
LeftJoin(join(AppOIDCConfigColumnAppID, AppColumnID)).
|
||||
LeftJoin(join(AppSAMLConfigColumnAppID, AppColumnID) + db.Timetravel(call.Took(ctx))).
|
||||
PlaceholderFormat(sq.Dollar), func(row *sql.Row) (*App, error) {
|
||||
app := new(App)
|
||||
LeftJoin(join(AppSAMLConfigColumnAppID, AppColumnID) + db.Timetravel(call.Took(ctx))),
|
||||
scanApp
|
||||
}
|
||||
|
||||
var (
|
||||
apiConfig = sqlAPIConfig{}
|
||||
oidcConfig = sqlOIDCConfig{}
|
||||
samlConfig = sqlSAMLConfig{}
|
||||
)
|
||||
func scanApp(row *sql.Row) (*App, error) {
|
||||
app := new(App)
|
||||
|
||||
err := row.Scan(
|
||||
&app.ID,
|
||||
&app.Name,
|
||||
&app.ProjectID,
|
||||
&app.CreationDate,
|
||||
&app.ChangeDate,
|
||||
&app.ResourceOwner,
|
||||
&app.State,
|
||||
&app.Sequence,
|
||||
var (
|
||||
apiConfig = sqlAPIConfig{}
|
||||
oidcConfig = sqlOIDCConfig{}
|
||||
samlConfig = sqlSAMLConfig{}
|
||||
)
|
||||
|
||||
&apiConfig.appID,
|
||||
&apiConfig.clientID,
|
||||
&apiConfig.authMethod,
|
||||
err := row.Scan(
|
||||
&app.ID,
|
||||
&app.Name,
|
||||
&app.ProjectID,
|
||||
&app.CreationDate,
|
||||
&app.ChangeDate,
|
||||
&app.ResourceOwner,
|
||||
&app.State,
|
||||
&app.Sequence,
|
||||
|
||||
&oidcConfig.appID,
|
||||
&oidcConfig.version,
|
||||
&oidcConfig.clientID,
|
||||
&oidcConfig.redirectUris,
|
||||
&oidcConfig.responseTypes,
|
||||
&oidcConfig.grantTypes,
|
||||
&oidcConfig.applicationType,
|
||||
&oidcConfig.authMethodType,
|
||||
&oidcConfig.postLogoutRedirectUris,
|
||||
&oidcConfig.devMode,
|
||||
&oidcConfig.accessTokenType,
|
||||
&oidcConfig.accessTokenRoleAssertion,
|
||||
&oidcConfig.iDTokenRoleAssertion,
|
||||
&oidcConfig.iDTokenUserinfoAssertion,
|
||||
&oidcConfig.clockSkew,
|
||||
&oidcConfig.additionalOrigins,
|
||||
&oidcConfig.skipNativeAppSuccessPage,
|
||||
&apiConfig.appID,
|
||||
&apiConfig.clientID,
|
||||
&apiConfig.authMethod,
|
||||
|
||||
&samlConfig.appID,
|
||||
&samlConfig.entityID,
|
||||
&samlConfig.metadata,
|
||||
&samlConfig.metadataURL,
|
||||
)
|
||||
&oidcConfig.appID,
|
||||
&oidcConfig.version,
|
||||
&oidcConfig.clientID,
|
||||
&oidcConfig.redirectUris,
|
||||
&oidcConfig.responseTypes,
|
||||
&oidcConfig.grantTypes,
|
||||
&oidcConfig.applicationType,
|
||||
&oidcConfig.authMethodType,
|
||||
&oidcConfig.postLogoutRedirectUris,
|
||||
&oidcConfig.devMode,
|
||||
&oidcConfig.accessTokenType,
|
||||
&oidcConfig.accessTokenRoleAssertion,
|
||||
&oidcConfig.iDTokenRoleAssertion,
|
||||
&oidcConfig.iDTokenUserinfoAssertion,
|
||||
&oidcConfig.clockSkew,
|
||||
&oidcConfig.additionalOrigins,
|
||||
&oidcConfig.skipNativeAppSuccessPage,
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, zerrors.ThrowNotFound(err, "QUERY-pCP8P", "Errors.App.NotExisting")
|
||||
}
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-4SJlx", "Errors.Internal")
|
||||
}
|
||||
&samlConfig.appID,
|
||||
&samlConfig.entityID,
|
||||
&samlConfig.metadata,
|
||||
&samlConfig.metadataURL,
|
||||
)
|
||||
|
||||
apiConfig.set(app)
|
||||
oidcConfig.set(app)
|
||||
samlConfig.set(app)
|
||||
|
||||
return app, nil
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, zerrors.ThrowNotFound(err, "QUERY-pCP8P", "Errors.App.NotExisting")
|
||||
}
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-4SJlx", "Errors.Internal")
|
||||
}
|
||||
|
||||
apiConfig.set(app)
|
||||
oidcConfig.set(app)
|
||||
samlConfig.set(app)
|
||||
|
||||
return app, nil
|
||||
}
|
||||
|
||||
func prepareOIDCAppQuery() (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
|
||||
@ -690,6 +717,8 @@ func prepareSAMLAppQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuil
|
||||
AppSAMLConfigColumnMetadataURL.identifier(),
|
||||
).From(appsTable.identifier()).
|
||||
Join(join(AppSAMLConfigColumnAppID, AppColumnID)).
|
||||
Join(join(ProjectColumnID, AppColumnProjectID)).
|
||||
Join(join(OrgColumnID, AppColumnResourceOwner)).
|
||||
PlaceholderFormat(sq.Dollar), func(row *sql.Row) (*App, error) {
|
||||
|
||||
app := new(App)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
@ -9,13 +10,15 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
var (
|
||||
expectedAppQuery = regexp.QuoteMeta(`SELECT projections.apps7.id,` +
|
||||
expectedAppQueryBase = `SELECT projections.apps7.id,` +
|
||||
` projections.apps7.name,` +
|
||||
` projections.apps7.project_id,` +
|
||||
` projections.apps7.creation_date,` +
|
||||
@ -53,8 +56,11 @@ var (
|
||||
` FROM projections.apps7` +
|
||||
` LEFT JOIN projections.apps7_api_configs ON projections.apps7.id = projections.apps7_api_configs.app_id AND projections.apps7.instance_id = projections.apps7_api_configs.instance_id` +
|
||||
` LEFT JOIN projections.apps7_oidc_configs ON projections.apps7.id = projections.apps7_oidc_configs.app_id AND projections.apps7.instance_id = projections.apps7_oidc_configs.instance_id` +
|
||||
` LEFT JOIN projections.apps7_saml_configs ON projections.apps7.id = projections.apps7_saml_configs.app_id AND projections.apps7.instance_id = projections.apps7_saml_configs.instance_id` +
|
||||
` AS OF SYSTEM TIME '-1 ms'`)
|
||||
` LEFT JOIN projections.apps7_saml_configs ON projections.apps7.id = projections.apps7_saml_configs.app_id AND projections.apps7.instance_id = projections.apps7_saml_configs.instance_id`
|
||||
expectedAppQuery = regexp.QuoteMeta(expectedAppQueryBase)
|
||||
expectedActiveAppQuery = regexp.QuoteMeta(expectedAppQueryBase +
|
||||
` LEFT JOIN projections.projects4 ON projections.apps7.project_id = projections.projects4.id AND projections.apps7.instance_id = projections.projects4.instance_id` +
|
||||
` LEFT JOIN projections.orgs1 ON projections.apps7.resource_owner = projections.orgs1.id AND projections.apps7.instance_id = projections.orgs1.instance_id`)
|
||||
expectedAppsQuery = regexp.QuoteMeta(`SELECT projections.apps7.id,` +
|
||||
` projections.apps7.name,` +
|
||||
` projections.apps7.project_id,` +
|
||||
@ -1140,8 +1146,10 @@ func Test_AppPrepare(t *testing.T) {
|
||||
object interface{}
|
||||
}{
|
||||
{
|
||||
name: "prepareAppQuery no result",
|
||||
prepare: prepareAppQuery,
|
||||
name: "prepareAppQuery no result",
|
||||
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
|
||||
return prepareAppQuery(ctx, db, false)
|
||||
},
|
||||
want: want{
|
||||
sqlExpectations: mockQueriesScanErr(
|
||||
expectedAppQuery,
|
||||
@ -1158,8 +1166,10 @@ func Test_AppPrepare(t *testing.T) {
|
||||
object: (*App)(nil),
|
||||
},
|
||||
{
|
||||
name: "prepareAppQuery found",
|
||||
prepare: prepareAppQuery,
|
||||
name: "prepareAppQuery found",
|
||||
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
|
||||
return prepareAppQuery(ctx, db, false)
|
||||
},
|
||||
want: want{
|
||||
sqlExpectations: mockQuery(
|
||||
expectedAppQuery,
|
||||
@ -1215,8 +1225,10 @@ func Test_AppPrepare(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareAppQuery api app",
|
||||
prepare: prepareAppQuery,
|
||||
name: "prepareAppQuery api app",
|
||||
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
|
||||
return prepareAppQuery(ctx, db, false)
|
||||
},
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
expectedAppQuery,
|
||||
@ -1278,8 +1290,10 @@ func Test_AppPrepare(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareAppQuery oidc app",
|
||||
prepare: prepareAppQuery,
|
||||
name: "prepareAppQuery oidc app",
|
||||
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
|
||||
return prepareAppQuery(ctx, db, false)
|
||||
},
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
expectedAppQuery,
|
||||
@ -1355,9 +1369,93 @@ func Test_AppPrepare(t *testing.T) {
|
||||
SkipNativeAppSuccessPage: false,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "prepareAppQuery saml app",
|
||||
prepare: prepareAppQuery,
|
||||
},
|
||||
{
|
||||
name: "prepareAppQuery oidc app active only",
|
||||
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
|
||||
return prepareAppQuery(ctx, db, true)
|
||||
},
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
expectedActiveAppQuery,
|
||||
appCols,
|
||||
[][]driver.Value{
|
||||
{
|
||||
"app-id",
|
||||
"app-name",
|
||||
"project-id",
|
||||
testNow,
|
||||
testNow,
|
||||
"ro",
|
||||
domain.AppStateActive,
|
||||
uint64(20211109),
|
||||
// api config
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// oidc config
|
||||
"app-id",
|
||||
domain.OIDCVersionV1,
|
||||
"oidc-client-id",
|
||||
database.TextArray[string]{"https://redirect.to/me"},
|
||||
database.NumberArray[domain.OIDCResponseType]{domain.OIDCResponseTypeIDTokenToken},
|
||||
database.NumberArray[domain.OIDCGrantType]{domain.OIDCGrantTypeImplicit},
|
||||
domain.OIDCApplicationTypeUserAgent,
|
||||
domain.OIDCAuthMethodTypeNone,
|
||||
database.TextArray[string]{"post.logout.ch"},
|
||||
true,
|
||||
domain.OIDCTokenTypeJWT,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
1 * time.Second,
|
||||
database.TextArray[string]{"additional.origin"},
|
||||
false,
|
||||
// saml config
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
object: &App{
|
||||
ID: "app-id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
ResourceOwner: "ro",
|
||||
State: domain.AppStateActive,
|
||||
Sequence: 20211109,
|
||||
Name: "app-name",
|
||||
ProjectID: "project-id",
|
||||
OIDCConfig: &OIDCApp{
|
||||
Version: domain.OIDCVersionV1,
|
||||
ClientID: "oidc-client-id",
|
||||
RedirectURIs: database.TextArray[string]{"https://redirect.to/me"},
|
||||
ResponseTypes: database.NumberArray[domain.OIDCResponseType]{domain.OIDCResponseTypeIDTokenToken},
|
||||
GrantTypes: database.NumberArray[domain.OIDCGrantType]{domain.OIDCGrantTypeImplicit},
|
||||
AppType: domain.OIDCApplicationTypeUserAgent,
|
||||
AuthMethodType: domain.OIDCAuthMethodTypeNone,
|
||||
PostLogoutRedirectURIs: database.TextArray[string]{"post.logout.ch"},
|
||||
IsDevMode: true,
|
||||
AccessTokenType: domain.OIDCTokenTypeJWT,
|
||||
AssertAccessTokenRole: true,
|
||||
AssertIDTokenRole: true,
|
||||
AssertIDTokenUserinfo: true,
|
||||
ClockSkew: 1 * time.Second,
|
||||
AdditionalOrigins: database.TextArray[string]{"additional.origin"},
|
||||
ComplianceProblems: nil,
|
||||
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
|
||||
SkipNativeAppSuccessPage: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareAppQuery saml app",
|
||||
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
|
||||
return prepareAppQuery(ctx, db, false)
|
||||
},
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
expectedAppQuery,
|
||||
@ -1420,8 +1518,10 @@ func Test_AppPrepare(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareAppQuery oidc app IsDevMode inactive",
|
||||
prepare: prepareAppQuery,
|
||||
name: "prepareAppQuery oidc app IsDevMode inactive",
|
||||
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
|
||||
return prepareAppQuery(ctx, db, false)
|
||||
},
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
expectedAppQuery,
|
||||
@ -1499,8 +1599,10 @@ func Test_AppPrepare(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareAppQuery oidc app AssertAccessTokenRole inactive",
|
||||
prepare: prepareAppQuery,
|
||||
name: "prepareAppQuery oidc app AssertAccessTokenRole inactive",
|
||||
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
|
||||
return prepareAppQuery(ctx, db, false)
|
||||
},
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
expectedAppQuery,
|
||||
@ -1578,8 +1680,10 @@ func Test_AppPrepare(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareAppQuery oidc app AssertIDTokenRole inactive",
|
||||
prepare: prepareAppQuery,
|
||||
name: "prepareAppQuery oidc app AssertIDTokenRole inactive",
|
||||
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
|
||||
return prepareAppQuery(ctx, db, false)
|
||||
},
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
expectedAppQuery,
|
||||
@ -1657,8 +1761,10 @@ func Test_AppPrepare(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareAppQuery oidc app AssertIDTokenUserinfo inactive",
|
||||
prepare: prepareAppQuery,
|
||||
name: "prepareAppQuery oidc app AssertIDTokenUserinfo inactive",
|
||||
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
|
||||
return prepareAppQuery(ctx, db, false)
|
||||
},
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
expectedAppQuery,
|
||||
@ -1736,8 +1842,10 @@ func Test_AppPrepare(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareAppQuery sql err",
|
||||
prepare: prepareAppQuery,
|
||||
name: "prepareAppQuery sql err",
|
||||
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
|
||||
return prepareAppQuery(ctx, db, false)
|
||||
},
|
||||
want: want{
|
||||
sqlExpectations: mockQueryErr(
|
||||
expectedAppQuery,
|
||||
|
@ -10,7 +10,6 @@ import (
|
||||
"time"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/call"
|
||||
@ -27,7 +26,7 @@ type Stateful interface {
|
||||
type State struct {
|
||||
LastRun time.Time
|
||||
|
||||
Position decimal.Decimal
|
||||
Position float64
|
||||
EventCreatedAt time.Time
|
||||
AggregateID string
|
||||
AggregateType eventstore.AggregateType
|
||||
@ -222,7 +221,7 @@ func prepareLatestState(ctx context.Context, db prepareDatabase) (sq.SelectBuild
|
||||
var (
|
||||
creationDate sql.NullTime
|
||||
lastUpdated sql.NullTime
|
||||
position decimal.NullDecimal
|
||||
position sql.NullFloat64
|
||||
)
|
||||
err := row.Scan(
|
||||
&creationDate,
|
||||
@ -235,7 +234,7 @@ func prepareLatestState(ctx context.Context, db prepareDatabase) (sq.SelectBuild
|
||||
return &State{
|
||||
EventCreatedAt: creationDate.Time,
|
||||
LastRun: lastUpdated.Time,
|
||||
Position: position.Decimal,
|
||||
Position: position.Float64,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
@ -260,7 +259,7 @@ func prepareCurrentStateQuery(ctx context.Context, db prepareDatabase) (sq.Selec
|
||||
var (
|
||||
lastRun sql.NullTime
|
||||
eventDate sql.NullTime
|
||||
currentPosition decimal.NullDecimal
|
||||
currentPosition sql.NullFloat64
|
||||
aggregateType sql.NullString
|
||||
aggregateID sql.NullString
|
||||
sequence sql.NullInt64
|
||||
@ -281,7 +280,7 @@ func prepareCurrentStateQuery(ctx context.Context, db prepareDatabase) (sq.Selec
|
||||
}
|
||||
currentState.State.EventCreatedAt = eventDate.Time
|
||||
currentState.State.LastRun = lastRun.Time
|
||||
currentState.Position = currentPosition.Decimal
|
||||
currentState.Position = currentPosition.Float64
|
||||
currentState.AggregateType = eventstore.AggregateType(aggregateType.String)
|
||||
currentState.AggregateID = aggregateID.String
|
||||
currentState.Sequence = uint64(sequence.Int64)
|
||||
|
@ -7,8 +7,6 @@ import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -89,7 +87,7 @@ func Test_CurrentSequencesPrepares(t *testing.T) {
|
||||
State: State{
|
||||
EventCreatedAt: testNow,
|
||||
LastRun: testNow,
|
||||
Position: decimal.NewFromInt(20211108),
|
||||
Position: 20211108,
|
||||
AggregateID: "agg-id",
|
||||
AggregateType: "agg-type",
|
||||
Sequence: 20211108,
|
||||
@ -136,7 +134,7 @@ func Test_CurrentSequencesPrepares(t *testing.T) {
|
||||
ProjectionName: "projection-name",
|
||||
State: State{
|
||||
EventCreatedAt: testNow,
|
||||
Position: decimal.NewFromInt(20211108),
|
||||
Position: 20211108,
|
||||
LastRun: testNow,
|
||||
AggregateID: "agg-id",
|
||||
AggregateType: "agg-type",
|
||||
@ -147,7 +145,7 @@ func Test_CurrentSequencesPrepares(t *testing.T) {
|
||||
ProjectionName: "projection-name2",
|
||||
State: State{
|
||||
EventCreatedAt: testNow,
|
||||
Position: decimal.NewFromInt(20211108),
|
||||
Position: 20211108,
|
||||
LastRun: testNow,
|
||||
AggregateID: "agg-id",
|
||||
AggregateType: "agg-type",
|
||||
|
@ -52,7 +52,7 @@ type IntrospectionClient struct {
|
||||
//go:embed introspection_client_by_id.sql
|
||||
var introspectionClientByIDQuery string
|
||||
|
||||
func (q *Queries) GetIntrospectionClientByID(ctx context.Context, clientID string, getKeys bool) (_ *IntrospectionClient, err error) {
|
||||
func (q *Queries) ActiveIntrospectionClientByID(ctx context.Context, clientID string, getKeys bool) (_ *IntrospectionClient, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
|
@ -20,6 +20,7 @@ keys as (
|
||||
)
|
||||
select config.app_id, config.client_id, config.client_secret, config.app_type, apps.project_id, apps.resource_owner, p.project_role_assertion, keys.public_keys
|
||||
from config
|
||||
join projections.apps7 apps on apps.id = config.app_id and apps.instance_id = config.instance_id
|
||||
join projections.projects4 p on p.id = apps.project_id and p.instance_id = $1
|
||||
join projections.apps7 apps on apps.id = config.app_id and apps.instance_id = config.instance_id and apps.state = 1
|
||||
join projections.projects4 p on p.id = apps.project_id and p.instance_id = $1 and p.state = 1
|
||||
join projections.orgs1 o on o.id = p.resource_owner and o.instance_id = config.instance_id and o.org_state = 1
|
||||
left join keys on keys.client_id = config.client_id;
|
||||
|
@ -14,7 +14,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
)
|
||||
|
||||
func TestQueries_GetIntrospectionClientByID(t *testing.T) {
|
||||
func TestQueries_ActiveIntrospectionClientByID(t *testing.T) {
|
||||
pubkeys := database.Map[[]byte]{
|
||||
"key1": {1, 2, 3},
|
||||
"key2": {4, 5, 6},
|
||||
@ -96,7 +96,7 @@ func TestQueries_GetIntrospectionClientByID(t *testing.T) {
|
||||
},
|
||||
}
|
||||
ctx := authz.NewMockContext("instanceID", "orgID", "userID")
|
||||
got, err := q.GetIntrospectionClientByID(ctx, tt.args.clientID, tt.args.getKeys)
|
||||
got, err := q.ActiveIntrospectionClientByID(ctx, tt.args.clientID, tt.args.getKeys)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
|
@ -45,7 +45,7 @@ type OIDCClient struct {
|
||||
//go:embed oidc_client_by_id.sql
|
||||
var oidcClientQuery string
|
||||
|
||||
func (q *Queries) GetOIDCClientByID(ctx context.Context, clientID string, getKeys bool) (client *OIDCClient, err error) {
|
||||
func (q *Queries) ActiveOIDCClientByID(ctx context.Context, clientID string, getKeys bool) (client *OIDCClient, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
|
@ -5,8 +5,9 @@ with client as (
|
||||
c.access_token_type, c.access_token_role_assertion, c.id_token_role_assertion,
|
||||
c.id_token_userinfo_assertion, c.clock_skew, c.additional_origins, a.project_id, p.project_role_assertion
|
||||
from projections.apps7_oidc_configs c
|
||||
join projections.apps7 a on a.id = c.app_id and a.instance_id = c.instance_id
|
||||
join projections.projects4 p on p.id = a.project_id and p.instance_id = a.instance_id
|
||||
join projections.apps7 a on a.id = c.app_id and a.instance_id = c.instance_id and a.state = 1
|
||||
join projections.projects4 p on p.id = a.project_id and p.instance_id = a.instance_id and p.state = 1
|
||||
join projections.orgs1 o on o.id = p.resource_owner and o.instance_id = c.instance_id and o.org_state = 1
|
||||
where c.instance_id = $1
|
||||
and c.client_id = $2
|
||||
),
|
||||
|
@ -29,7 +29,7 @@ var (
|
||||
testdataOidcClientNoSettings string
|
||||
)
|
||||
|
||||
func TestQueries_GetOIDCClientByID(t *testing.T) {
|
||||
func TestQueries_ActiveOIDCClientByID(t *testing.T) {
|
||||
expQuery := regexp.QuoteMeta(oidcClientQuery)
|
||||
cols := []string{"client"}
|
||||
pubkey := `-----BEGIN RSA PUBLIC KEY-----
|
||||
@ -232,7 +232,7 @@ low2kyJov38V4Uk2I8kuXpLcnrpw5Tio2ooiUE27b0vHZqBKOei9Uo88qCrn3EKx
|
||||
},
|
||||
}
|
||||
ctx := authz.NewMockContext("instanceID", "orgID", "loginClient")
|
||||
got, err := q.GetOIDCClientByID(ctx, "clientID", true)
|
||||
got, err := q.ActiveOIDCClientByID(ctx, "clientID", true)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
SMTPConfigProjectionTable = "projections.smtp_configs3"
|
||||
SMTPConfigProjectionTable = "projections.smtp_configs4"
|
||||
SMTPConfigTable = SMTPConfigProjectionTable + "_" + smtpConfigSMTPTableSuffix
|
||||
SMTPConfigHTTPTable = SMTPConfigProjectionTable + "_" + smtpConfigHTTPTableSuffix
|
||||
|
||||
@ -174,7 +174,7 @@ func (p *smtpConfigProjection) reduceSMTPConfigAdded(event eventstore.Event) (*h
|
||||
handler.AddCreateStatement(
|
||||
[]handler.Column{
|
||||
handler.NewCol(SMTPConfigSMTPColumnInstanceID, e.Aggregate().InstanceID),
|
||||
handler.NewCol(SMTPConfigSMTPColumnID, e.ID),
|
||||
handler.NewCol(SMTPConfigSMTPColumnID, id),
|
||||
handler.NewCol(SMTPConfigSMTPColumnTLS, e.TLS),
|
||||
handler.NewCol(SMTPConfigSMTPColumnSenderAddress, e.SenderAddress),
|
||||
handler.NewCol(SMTPConfigSMTPColumnSenderName, e.SenderName),
|
||||
|
@ -50,7 +50,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.smtp_configs3 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedStmt: "UPDATE projections.smtp_configs4 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -60,7 +60,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.smtp_configs3_smtp SET (tls, sender_address, sender_name, reply_to_address, host, username) = ($1, $2, $3, $4, $5, $6) WHERE (id = $7) AND (instance_id = $8)",
|
||||
expectedStmt: "UPDATE projections.smtp_configs4_smtp SET (tls, sender_address, sender_name, reply_to_address, host, username) = ($1, $2, $3, $4, $5, $6) WHERE (id = $7) AND (instance_id = $8)",
|
||||
expectedArgs: []interface{}{
|
||||
true,
|
||||
"sender",
|
||||
@ -100,7 +100,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.smtp_configs3 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedStmt: "UPDATE projections.smtp_configs4 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -137,7 +137,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.smtp_configs3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.smtp_configs4 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -146,7 +146,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.smtp_configs3_smtp SET sender_address = $1 WHERE (id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.smtp_configs4_smtp SET sender_address = $1 WHERE (id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
"sender",
|
||||
"config-id",
|
||||
@ -182,7 +182,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.smtp_configs3 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedStmt: "UPDATE projections.smtp_configs4 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -192,7 +192,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.smtp_configs3_http SET endpoint = $1 WHERE (id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.smtp_configs4_http SET endpoint = $1 WHERE (id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
"endpoint",
|
||||
"config-id",
|
||||
@ -227,7 +227,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.smtp_configs3 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedStmt: "UPDATE projections.smtp_configs4 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -264,7 +264,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.smtp_configs3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.smtp_configs4 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -273,7 +273,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.smtp_configs3_http SET endpoint = $1 WHERE (id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.smtp_configs4_http SET endpoint = $1 WHERE (id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
"endpoint",
|
||||
"config-id",
|
||||
@ -284,6 +284,69 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "reduceSMTPConfigAdded (no id)",
|
||||
args: args{
|
||||
event: getEvent(
|
||||
testEvent(
|
||||
instance.SMTPConfigAddedEventType,
|
||||
instance.AggregateType,
|
||||
[]byte(`{
|
||||
"instance_id": "instance-id",
|
||||
"resource_owner": "ro-id",
|
||||
"aggregate_id": "agg-id",
|
||||
"tls": true,
|
||||
"senderAddress": "sender",
|
||||
"senderName": "name",
|
||||
"replyToAddress": "reply-to",
|
||||
"host": "host",
|
||||
"user": "user",
|
||||
"password": {
|
||||
"cryptoType": 0,
|
||||
"algorithm": "RSA-265",
|
||||
"keyId": "key-id"
|
||||
}
|
||||
}`),
|
||||
), eventstore.GenericEventMapper[instance.SMTPConfigAddedEvent]),
|
||||
},
|
||||
reduce: (&smtpConfigProjection{}).reduceSMTPConfigAdded,
|
||||
want: wantReduce{
|
||||
aggregateType: eventstore.AggregateType("instance"),
|
||||
sequence: 15,
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.smtp_configs4 (creation_date, change_date, instance_id, resource_owner, aggregate_id, id, sequence, state, description) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
anyArg{},
|
||||
"instance-id",
|
||||
"ro-id",
|
||||
"agg-id",
|
||||
"ro-id",
|
||||
uint64(15),
|
||||
domain.SMTPConfigStateActive,
|
||||
"generic",
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.smtp_configs4_smtp (instance_id, id, tls, sender_address, sender_name, reply_to_address, host, username, password) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedArgs: []interface{}{
|
||||
"instance-id",
|
||||
"ro-id",
|
||||
true,
|
||||
"sender",
|
||||
"name",
|
||||
"reply-to",
|
||||
"host",
|
||||
"user",
|
||||
anyArg{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "reduceSMTPConfigAdded",
|
||||
args: args{
|
||||
@ -318,7 +381,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.smtp_configs3 (creation_date, change_date, instance_id, resource_owner, aggregate_id, id, sequence, state, description) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedStmt: "INSERT INTO projections.smtp_configs4 (creation_date, change_date, instance_id, resource_owner, aggregate_id, id, sequence, state, description) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
anyArg{},
|
||||
@ -332,7 +395,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.smtp_configs3_smtp (instance_id, id, tls, sender_address, sender_name, reply_to_address, host, username, password) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedStmt: "INSERT INTO projections.smtp_configs4_smtp (instance_id, id, tls, sender_address, sender_name, reply_to_address, host, username, password) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedArgs: []interface{}{
|
||||
"instance-id",
|
||||
"config-id",
|
||||
@ -374,7 +437,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.smtp_configs3 (creation_date, change_date, instance_id, resource_owner, aggregate_id, id, sequence, state, description) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedStmt: "INSERT INTO projections.smtp_configs4 (creation_date, change_date, instance_id, resource_owner, aggregate_id, id, sequence, state, description) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
anyArg{},
|
||||
@ -388,7 +451,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.smtp_configs3_http (instance_id, id, endpoint) VALUES ($1, $2, $3)",
|
||||
expectedStmt: "INSERT INTO projections.smtp_configs4_http (instance_id, id, endpoint) VALUES ($1, $2, $3)",
|
||||
expectedArgs: []interface{}{
|
||||
"instance-id",
|
||||
"config-id",
|
||||
@ -420,7 +483,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.smtp_configs3 SET (change_date, sequence, state) = ($1, $2, $3) WHERE (NOT (id = $4)) AND (state = $5) AND (instance_id = $6)",
|
||||
expectedStmt: "UPDATE projections.smtp_configs4 SET (change_date, sequence, state) = ($1, $2, $3) WHERE (NOT (id = $4)) AND (state = $5) AND (instance_id = $6)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -431,7 +494,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.smtp_configs3 SET (change_date, sequence, state) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedStmt: "UPDATE projections.smtp_configs4 SET (change_date, sequence, state) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -465,7 +528,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.smtp_configs3 SET (change_date, sequence, state) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedStmt: "UPDATE projections.smtp_configs4 SET (change_date, sequence, state) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -505,7 +568,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.smtp_configs3_smtp SET password = $1 WHERE (id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.smtp_configs4_smtp SET password = $1 WHERE (id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
"config-id",
|
||||
@ -513,7 +576,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.smtp_configs3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.smtp_configs4 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -546,7 +609,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "DELETE FROM projections.smtp_configs3 WHERE (id = $1) AND (instance_id = $2)",
|
||||
expectedStmt: "DELETE FROM projections.smtp_configs4 WHERE (id = $1) AND (instance_id = $2)",
|
||||
expectedArgs: []interface{}{
|
||||
"config-id",
|
||||
"instance-id",
|
||||
@ -573,7 +636,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "DELETE FROM projections.smtp_configs3 WHERE (instance_id = $1)",
|
||||
expectedStmt: "DELETE FROM projections.smtp_configs4 WHERE (instance_id = $1)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
},
|
||||
|
@ -14,26 +14,26 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
prepareSMTPConfigStmt = `SELECT projections.smtp_configs3.creation_date,` +
|
||||
` projections.smtp_configs3.change_date,` +
|
||||
` projections.smtp_configs3.resource_owner,` +
|
||||
` projections.smtp_configs3.sequence,` +
|
||||
` projections.smtp_configs3.id,` +
|
||||
` projections.smtp_configs3.state,` +
|
||||
` projections.smtp_configs3.description,` +
|
||||
` projections.smtp_configs3_smtp.id,` +
|
||||
` projections.smtp_configs3_smtp.tls,` +
|
||||
` projections.smtp_configs3_smtp.sender_address,` +
|
||||
` projections.smtp_configs3_smtp.sender_name,` +
|
||||
` projections.smtp_configs3_smtp.reply_to_address,` +
|
||||
` projections.smtp_configs3_smtp.host,` +
|
||||
` projections.smtp_configs3_smtp.username,` +
|
||||
` projections.smtp_configs3_smtp.password,` +
|
||||
` projections.smtp_configs3_http.id,` +
|
||||
` projections.smtp_configs3_http.endpoint` +
|
||||
` FROM projections.smtp_configs3` +
|
||||
` LEFT JOIN projections.smtp_configs3_smtp ON projections.smtp_configs3.id = projections.smtp_configs3_smtp.id AND projections.smtp_configs3.instance_id = projections.smtp_configs3_smtp.instance_id` +
|
||||
` LEFT JOIN projections.smtp_configs3_http ON projections.smtp_configs3.id = projections.smtp_configs3_http.id AND projections.smtp_configs3.instance_id = projections.smtp_configs3_http.instance_id` +
|
||||
prepareSMTPConfigStmt = `SELECT projections.smtp_configs4.creation_date,` +
|
||||
` projections.smtp_configs4.change_date,` +
|
||||
` projections.smtp_configs4.resource_owner,` +
|
||||
` projections.smtp_configs4.sequence,` +
|
||||
` projections.smtp_configs4.id,` +
|
||||
` projections.smtp_configs4.state,` +
|
||||
` projections.smtp_configs4.description,` +
|
||||
` projections.smtp_configs4_smtp.id,` +
|
||||
` projections.smtp_configs4_smtp.tls,` +
|
||||
` projections.smtp_configs4_smtp.sender_address,` +
|
||||
` projections.smtp_configs4_smtp.sender_name,` +
|
||||
` projections.smtp_configs4_smtp.reply_to_address,` +
|
||||
` projections.smtp_configs4_smtp.host,` +
|
||||
` projections.smtp_configs4_smtp.username,` +
|
||||
` projections.smtp_configs4_smtp.password,` +
|
||||
` projections.smtp_configs4_http.id,` +
|
||||
` projections.smtp_configs4_http.endpoint` +
|
||||
` FROM projections.smtp_configs4` +
|
||||
` LEFT JOIN projections.smtp_configs4_smtp ON projections.smtp_configs4.id = projections.smtp_configs4_smtp.id AND projections.smtp_configs4.instance_id = projections.smtp_configs4_smtp.instance_id` +
|
||||
` LEFT JOIN projections.smtp_configs4_http ON projections.smtp_configs4.id = projections.smtp_configs4_http.id AND projections.smtp_configs4.instance_id = projections.smtp_configs4_http.instance_id` +
|
||||
` AS OF SYSTEM TIME '-1 ms'`
|
||||
prepareSMTPConfigCols = []string{
|
||||
"creation_date",
|
||||
|
@ -143,6 +143,10 @@ func NewUserGrantRoleQuery(value string) (SearchQuery, error) {
|
||||
return NewTextQuery(UserGrantRoles, value, TextListContains)
|
||||
}
|
||||
|
||||
func NewUserGrantStateQuery(value domain.UserGrantState) (SearchQuery, error) {
|
||||
return NewNumberQuery(UserGrantState, value, NumberEquals)
|
||||
}
|
||||
|
||||
func NewUserGrantWithGrantedQuery(owner string) (SearchQuery, error) {
|
||||
orgQuery, err := NewUserGrantResourceOwnerSearchQuery(owner)
|
||||
if err != nil {
|
||||
@ -277,7 +281,7 @@ func (q *Queries) UserGrants(ctx context.Context, queries *UserGrantsQueries, sh
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-wXnQR", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
latestState, err := q.latestState(ctx, userGrantTable)
|
||||
latestSequence, err := q.latestState(ctx, userGrantTable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -290,7 +294,7 @@ func (q *Queries) UserGrants(ctx context.Context, queries *UserGrantsQueries, sh
|
||||
return nil, err
|
||||
}
|
||||
|
||||
grants.State = latestState
|
||||
grants.State = latestSequence
|
||||
return grants, nil
|
||||
}
|
||||
|
||||
|
@ -144,7 +144,7 @@ func (q *Queries) Memberships(ctx context.Context, queries *MembershipSearchQuer
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInvalidArgument(err, "QUERY-T84X9", "Errors.Query.InvalidRequest")
|
||||
}
|
||||
latestState, err := q.latestState(ctx, orgMemberTable, instanceMemberTable, projectMemberTable, projectGrantMemberTable)
|
||||
latestSequence, err := q.latestState(ctx, orgMemberTable, instanceMemberTable, projectMemberTable, projectGrantMemberTable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -157,7 +157,7 @@ func (q *Queries) Memberships(ctx context.Context, queries *MembershipSearchQuer
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
memberships.State = latestState
|
||||
memberships.State = latestSequence
|
||||
return memberships, nil
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ with usr as (
|
||||
select u.id, u.creation_date, u.change_date, u.sequence, u.state, u.resource_owner, u.username, n.login_name as preferred_login_name
|
||||
from projections.users13 u
|
||||
left join projections.login_names3 n on u.id = n.user_id and u.instance_id = n.instance_id
|
||||
where u.id = $1
|
||||
where u.id = $1 and u.state = 1 -- only allow active users
|
||||
and u.instance_id = $2
|
||||
and n.is_primary = true
|
||||
),
|
||||
@ -38,6 +38,7 @@ user_grants as (
|
||||
where user_id = $1
|
||||
and instance_id = $2
|
||||
and project_id = any($3)
|
||||
and state = 1
|
||||
{{ if . -}}
|
||||
and resource_owner = any($4)
|
||||
{{- end }}
|
||||
|
@ -3,7 +3,6 @@ package database
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/zitadel/logging"
|
||||
"golang.org/x/exp/constraints"
|
||||
)
|
||||
@ -95,7 +94,7 @@ func (c numberCompare) String() string {
|
||||
}
|
||||
|
||||
type number interface {
|
||||
constraints.Integer | constraints.Float | time.Time | decimal.Decimal
|
||||
constraints.Integer | constraints.Float | time.Time
|
||||
// TODO: condition must know if it's args are named parameters or not
|
||||
// constraints.Integer | constraints.Float | time.Time | placeholder
|
||||
}
|
||||
|
@ -2,8 +2,6 @@ package eventstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
func NewEventstore(querier Querier, pusher Pusher) *EventStore {
|
||||
@ -32,12 +30,12 @@ type healthier interface {
|
||||
}
|
||||
|
||||
type GlobalPosition struct {
|
||||
Position decimal.Decimal
|
||||
Position float64
|
||||
InPositionOrder uint32
|
||||
}
|
||||
|
||||
func (gp GlobalPosition) IsLess(other GlobalPosition) bool {
|
||||
return gp.Position.LessThan(other.Position) || (gp.Position.Equal(other.Position) && gp.InPositionOrder < other.InPositionOrder)
|
||||
return gp.Position < other.Position || (gp.Position == other.Position && gp.InPositionOrder < other.InPositionOrder)
|
||||
}
|
||||
|
||||
type Reducer interface {
|
||||
|
@ -8,8 +8,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/v2/database/mock"
|
||||
"github.com/zitadel/zitadel/internal/v2/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
@ -820,7 +818,7 @@ func Test_push(t *testing.T) {
|
||||
[][]driver.Value{
|
||||
{
|
||||
time.Now(),
|
||||
decimal.NewFromFloat(123).String(),
|
||||
float64(123),
|
||||
},
|
||||
},
|
||||
),
|
||||
@ -901,11 +899,11 @@ func Test_push(t *testing.T) {
|
||||
[][]driver.Value{
|
||||
{
|
||||
time.Now(),
|
||||
decimal.NewFromFloat(123).String(),
|
||||
float64(123),
|
||||
},
|
||||
{
|
||||
time.Now(),
|
||||
decimal.NewFromFloat(123.1).String(),
|
||||
float64(123.1),
|
||||
},
|
||||
},
|
||||
),
|
||||
@ -986,11 +984,11 @@ func Test_push(t *testing.T) {
|
||||
[][]driver.Value{
|
||||
{
|
||||
time.Now(),
|
||||
decimal.NewFromFloat(123).String(),
|
||||
float64(123),
|
||||
},
|
||||
{
|
||||
time.Now(),
|
||||
decimal.NewFromFloat(123.1).String(),
|
||||
float64(123.1),
|
||||
},
|
||||
},
|
||||
),
|
||||
@ -1046,7 +1044,7 @@ func Test_push(t *testing.T) {
|
||||
[][]driver.Value{
|
||||
{
|
||||
time.Now(),
|
||||
decimal.NewFromFloat(123).String(),
|
||||
float64(123),
|
||||
},
|
||||
},
|
||||
),
|
||||
@ -1101,7 +1099,7 @@ func Test_push(t *testing.T) {
|
||||
[][]driver.Value{
|
||||
{
|
||||
time.Now(),
|
||||
decimal.NewFromFloat(123).String(),
|
||||
float64(123),
|
||||
},
|
||||
},
|
||||
),
|
||||
@ -1183,11 +1181,11 @@ func Test_push(t *testing.T) {
|
||||
[][]driver.Value{
|
||||
{
|
||||
time.Now(),
|
||||
decimal.NewFromFloat(123).String(),
|
||||
float64(123),
|
||||
},
|
||||
{
|
||||
time.Now(),
|
||||
decimal.NewFromFloat(123.1).String(),
|
||||
float64(123.1),
|
||||
},
|
||||
},
|
||||
),
|
||||
@ -1274,11 +1272,11 @@ func Test_push(t *testing.T) {
|
||||
[][]driver.Value{
|
||||
{
|
||||
time.Now(),
|
||||
decimal.NewFromFloat(123).String(),
|
||||
float64(123),
|
||||
},
|
||||
{
|
||||
time.Now(),
|
||||
decimal.NewFromFloat(123.1).String(),
|
||||
float64(123.1),
|
||||
},
|
||||
},
|
||||
),
|
||||
|
@ -8,8 +8,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/v2/database"
|
||||
"github.com/zitadel/zitadel/internal/v2/database/mock"
|
||||
"github.com/zitadel/zitadel/internal/v2/eventstore"
|
||||
@ -543,13 +541,13 @@ func Test_writeFilter(t *testing.T) {
|
||||
args: args{
|
||||
filter: eventstore.NewFilter(
|
||||
eventstore.FilterPagination(
|
||||
eventstore.PositionGreater(decimal.NewFromFloat(123.4), 0),
|
||||
eventstore.PositionGreater(123.4, 0),
|
||||
),
|
||||
),
|
||||
},
|
||||
want: wantQuery{
|
||||
query: " WHERE instance_id = $1 AND position > $2 ORDER BY position, in_tx_order",
|
||||
args: []any{"i1", decimal.NewFromFloat(123.4)},
|
||||
args: []any{"i1", 123.4},
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -557,18 +555,18 @@ func Test_writeFilter(t *testing.T) {
|
||||
args: args{
|
||||
filter: eventstore.NewFilter(
|
||||
eventstore.FilterPagination(
|
||||
// eventstore.PositionGreater(decimal.NewFromFloat(123.4), 0),
|
||||
// eventstore.PositionGreater(123.4, 0),
|
||||
// eventstore.PositionLess(125.4, 10),
|
||||
eventstore.PositionBetween(
|
||||
&eventstore.GlobalPosition{Position: decimal.NewFromFloat(123.4)},
|
||||
&eventstore.GlobalPosition{Position: decimal.NewFromFloat(125.4), InPositionOrder: 10},
|
||||
&eventstore.GlobalPosition{Position: 123.4},
|
||||
&eventstore.GlobalPosition{Position: 125.4, InPositionOrder: 10},
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
want: wantQuery{
|
||||
query: " WHERE instance_id = $1 AND ((position = $2 AND in_tx_order < $3) OR position < $4) AND position > $5 ORDER BY position, in_tx_order",
|
||||
args: []any{"i1", decimal.NewFromFloat(125.4), uint32(10), decimal.NewFromFloat(125.4), decimal.NewFromFloat(123.4)},
|
||||
args: []any{"i1", 125.4, uint32(10), 125.4, 123.4},
|
||||
// TODO: (adlerhurst) would require some refactoring to reuse existing args
|
||||
// query: " WHERE instance_id = $1 AND position > $2 AND ((position = $3 AND in_tx_order < $4) OR position < $3) ORDER BY position, in_tx_order",
|
||||
// args: []any{"i1", 123.4, 125.4, uint32(10)},
|
||||
@ -579,13 +577,13 @@ func Test_writeFilter(t *testing.T) {
|
||||
args: args{
|
||||
filter: eventstore.NewFilter(
|
||||
eventstore.FilterPagination(
|
||||
eventstore.PositionGreater(decimal.NewFromFloat(123.4), 12),
|
||||
eventstore.PositionGreater(123.4, 12),
|
||||
),
|
||||
),
|
||||
},
|
||||
want: wantQuery{
|
||||
query: " WHERE instance_id = $1 AND ((position = $2 AND in_tx_order > $3) OR position > $4) ORDER BY position, in_tx_order",
|
||||
args: []any{"i1", decimal.NewFromFloat(123.4), uint32(12), decimal.NewFromFloat(123.4)},
|
||||
args: []any{"i1", 123.4, uint32(12), 123.4},
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -595,13 +593,13 @@ func Test_writeFilter(t *testing.T) {
|
||||
eventstore.FilterPagination(
|
||||
eventstore.Limit(10),
|
||||
eventstore.Offset(3),
|
||||
eventstore.PositionGreater(decimal.NewFromFloat(123.4), 12),
|
||||
eventstore.PositionGreater(123.4, 12),
|
||||
),
|
||||
),
|
||||
},
|
||||
want: wantQuery{
|
||||
query: " WHERE instance_id = $1 AND ((position = $2 AND in_tx_order > $3) OR position > $4) ORDER BY position, in_tx_order LIMIT $5 OFFSET $6",
|
||||
args: []any{"i1", decimal.NewFromFloat(123.4), uint32(12), decimal.NewFromFloat(123.4), uint32(10), uint32(3)},
|
||||
args: []any{"i1", 123.4, uint32(12), 123.4, uint32(10), uint32(3)},
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -611,14 +609,14 @@ func Test_writeFilter(t *testing.T) {
|
||||
eventstore.FilterPagination(
|
||||
eventstore.Limit(10),
|
||||
eventstore.Offset(3),
|
||||
eventstore.PositionGreater(decimal.NewFromFloat(123.4), 12),
|
||||
eventstore.PositionGreater(123.4, 12),
|
||||
),
|
||||
eventstore.AppendAggregateFilter("user"),
|
||||
),
|
||||
},
|
||||
want: wantQuery{
|
||||
query: " WHERE instance_id = $1 AND aggregate_type = $2 AND ((position = $3 AND in_tx_order > $4) OR position > $5) ORDER BY position, in_tx_order LIMIT $6 OFFSET $7",
|
||||
args: []any{"i1", "user", decimal.NewFromFloat(123.4), uint32(12), decimal.NewFromFloat(123.4), uint32(10), uint32(3)},
|
||||
args: []any{"i1", "user", 123.4, uint32(12), 123.4, uint32(10), uint32(3)},
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -628,7 +626,7 @@ func Test_writeFilter(t *testing.T) {
|
||||
eventstore.FilterPagination(
|
||||
eventstore.Limit(10),
|
||||
eventstore.Offset(3),
|
||||
eventstore.PositionGreater(decimal.NewFromFloat(123.4), 12),
|
||||
eventstore.PositionGreater(123.4, 12),
|
||||
),
|
||||
eventstore.AppendAggregateFilter("user"),
|
||||
eventstore.AppendAggregateFilter(
|
||||
@ -639,7 +637,7 @@ func Test_writeFilter(t *testing.T) {
|
||||
},
|
||||
want: wantQuery{
|
||||
query: " WHERE instance_id = $1 AND (aggregate_type = $2 OR (aggregate_type = $3 AND aggregate_id = $4)) AND ((position = $5 AND in_tx_order > $6) OR position > $7) ORDER BY position, in_tx_order LIMIT $8 OFFSET $9",
|
||||
args: []any{"i1", "user", "org", "o1", decimal.NewFromFloat(123.4), uint32(12), decimal.NewFromFloat(123.4), uint32(10), uint32(3)},
|
||||
args: []any{"i1", "user", "org", "o1", 123.4, uint32(12), 123.4, uint32(10), uint32(3)},
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -958,7 +956,7 @@ func Test_writeQueryUse_examples(t *testing.T) {
|
||||
),
|
||||
eventstore.FilterPagination(
|
||||
// used because we need to check for first login and an app which is not console
|
||||
eventstore.PositionGreater(decimal.NewFromInt(12), 4),
|
||||
eventstore.PositionGreater(12, 4),
|
||||
),
|
||||
),
|
||||
eventstore.NewFilter(
|
||||
@ -1067,9 +1065,9 @@ func Test_writeQueryUse_examples(t *testing.T) {
|
||||
"instance",
|
||||
"user",
|
||||
"user.token.added",
|
||||
decimal.NewFromInt(12),
|
||||
float64(12),
|
||||
uint32(4),
|
||||
decimal.NewFromInt(12),
|
||||
float64(12),
|
||||
"instance",
|
||||
"instance",
|
||||
[]string{"instance.idp.config.added", "instance.idp.oauth.added", "instance.idp.oidc.added", "instance.idp.jwt.added", "instance.idp.azure.added", "instance.idp.github.added", "instance.idp.github.enterprise.added", "instance.idp.gitlab.added", "instance.idp.gitlab.selfhosted.added", "instance.idp.google.added", "instance.idp.ldap.added", "instance.idp.config.apple.added", "instance.idp.saml.added"},
|
||||
@ -1203,7 +1201,7 @@ func Test_executeQuery(t *testing.T) {
|
||||
time.Now(),
|
||||
"event.type",
|
||||
uint32(23),
|
||||
decimal.NewFromInt(123).String(),
|
||||
float64(123),
|
||||
uint32(0),
|
||||
nil,
|
||||
"gigi",
|
||||
@ -1237,7 +1235,7 @@ func Test_executeQuery(t *testing.T) {
|
||||
time.Now(),
|
||||
"event.type",
|
||||
uint32(23),
|
||||
decimal.NewFromInt(123).String(),
|
||||
float64(123),
|
||||
uint32(0),
|
||||
[]byte(`{"name": "gigi"}`),
|
||||
"gigi",
|
||||
@ -1271,7 +1269,7 @@ func Test_executeQuery(t *testing.T) {
|
||||
time.Now(),
|
||||
"event.type",
|
||||
uint32(23),
|
||||
decimal.NewFromInt(123).String(),
|
||||
float64(123),
|
||||
uint32(0),
|
||||
nil,
|
||||
"gigi",
|
||||
@ -1285,7 +1283,7 @@ func Test_executeQuery(t *testing.T) {
|
||||
time.Now(),
|
||||
"event.type",
|
||||
uint32(24),
|
||||
decimal.NewFromInt(124).String(),
|
||||
float64(124),
|
||||
uint32(0),
|
||||
[]byte(`{"name": "gigi"}`),
|
||||
"gigi",
|
||||
@ -1319,7 +1317,7 @@ func Test_executeQuery(t *testing.T) {
|
||||
time.Now(),
|
||||
"event.type",
|
||||
uint32(23),
|
||||
decimal.NewFromInt(123).String(),
|
||||
float64(123),
|
||||
uint32(0),
|
||||
nil,
|
||||
"gigi",
|
||||
@ -1333,7 +1331,7 @@ func Test_executeQuery(t *testing.T) {
|
||||
time.Now(),
|
||||
"event.type",
|
||||
uint32(24),
|
||||
decimal.NewFromInt(124).String(),
|
||||
float64(124),
|
||||
uint32(0),
|
||||
[]byte(`{"name": "gigi"}`),
|
||||
"gigi",
|
||||
|
@ -7,8 +7,6 @@ import (
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/v2/database"
|
||||
)
|
||||
|
||||
@ -725,7 +723,7 @@ func (pc *PositionCondition) Min() *GlobalPosition {
|
||||
// PositionGreater prepares the condition as follows
|
||||
// if inPositionOrder is set: position = AND in_tx_order > OR or position >
|
||||
// if inPositionOrder is NOT set: position >
|
||||
func PositionGreater(position decimal.Decimal, inPositionOrder uint32) paginationOpt {
|
||||
func PositionGreater(position float64, inPositionOrder uint32) paginationOpt {
|
||||
return func(p *Pagination) {
|
||||
p.ensurePosition()
|
||||
p.position.min = &GlobalPosition{
|
||||
@ -745,7 +743,7 @@ func GlobalPositionGreater(position *GlobalPosition) paginationOpt {
|
||||
// PositionLess prepares the condition as follows
|
||||
// if inPositionOrder is set: position = AND in_tx_order > OR or position >
|
||||
// if inPositionOrder is NOT set: position >
|
||||
func PositionLess(position decimal.Decimal, inPositionOrder uint32) paginationOpt {
|
||||
func PositionLess(position float64, inPositionOrder uint32) paginationOpt {
|
||||
return func(p *Pagination) {
|
||||
p.ensurePosition()
|
||||
p.position.max = &GlobalPosition{
|
||||
|
@ -6,8 +6,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/v2/database"
|
||||
)
|
||||
|
||||
@ -76,13 +74,13 @@ func TestPaginationOpt(t *testing.T) {
|
||||
name: "global position greater",
|
||||
args: args{
|
||||
opts: []paginationOpt{
|
||||
GlobalPositionGreater(&GlobalPosition{Position: decimal.NewFromInt(10)}),
|
||||
GlobalPositionGreater(&GlobalPosition{Position: 10}),
|
||||
},
|
||||
},
|
||||
want: &Pagination{
|
||||
position: &PositionCondition{
|
||||
min: &GlobalPosition{
|
||||
Position: decimal.NewFromInt(10),
|
||||
Position: 10,
|
||||
InPositionOrder: 0,
|
||||
},
|
||||
},
|
||||
@ -92,13 +90,13 @@ func TestPaginationOpt(t *testing.T) {
|
||||
name: "position greater",
|
||||
args: args{
|
||||
opts: []paginationOpt{
|
||||
PositionGreater(decimal.NewFromInt(10), 0),
|
||||
PositionGreater(10, 0),
|
||||
},
|
||||
},
|
||||
want: &Pagination{
|
||||
position: &PositionCondition{
|
||||
min: &GlobalPosition{
|
||||
Position: decimal.NewFromInt(10),
|
||||
Position: 10,
|
||||
InPositionOrder: 0,
|
||||
},
|
||||
},
|
||||
@ -109,13 +107,13 @@ func TestPaginationOpt(t *testing.T) {
|
||||
name: "position less",
|
||||
args: args{
|
||||
opts: []paginationOpt{
|
||||
PositionLess(decimal.NewFromInt(10), 12),
|
||||
PositionLess(10, 12),
|
||||
},
|
||||
},
|
||||
want: &Pagination{
|
||||
position: &PositionCondition{
|
||||
max: &GlobalPosition{
|
||||
Position: decimal.NewFromInt(10),
|
||||
Position: 10,
|
||||
InPositionOrder: 12,
|
||||
},
|
||||
},
|
||||
@ -125,13 +123,13 @@ func TestPaginationOpt(t *testing.T) {
|
||||
name: "global position less",
|
||||
args: args{
|
||||
opts: []paginationOpt{
|
||||
GlobalPositionLess(&GlobalPosition{Position: decimal.NewFromInt(12), InPositionOrder: 24}),
|
||||
GlobalPositionLess(&GlobalPosition{Position: 12, InPositionOrder: 24}),
|
||||
},
|
||||
},
|
||||
want: &Pagination{
|
||||
position: &PositionCondition{
|
||||
max: &GlobalPosition{
|
||||
Position: decimal.NewFromInt(12),
|
||||
Position: 12,
|
||||
InPositionOrder: 24,
|
||||
},
|
||||
},
|
||||
@ -142,19 +140,19 @@ func TestPaginationOpt(t *testing.T) {
|
||||
args: args{
|
||||
opts: []paginationOpt{
|
||||
PositionBetween(
|
||||
&GlobalPosition{decimal.NewFromInt(10), 12},
|
||||
&GlobalPosition{decimal.NewFromInt(20), 0},
|
||||
&GlobalPosition{10, 12},
|
||||
&GlobalPosition{20, 0},
|
||||
),
|
||||
},
|
||||
},
|
||||
want: &Pagination{
|
||||
position: &PositionCondition{
|
||||
min: &GlobalPosition{
|
||||
Position: decimal.NewFromInt(10),
|
||||
Position: 10,
|
||||
InPositionOrder: 12,
|
||||
},
|
||||
max: &GlobalPosition{
|
||||
Position: decimal.NewFromInt(20),
|
||||
Position: 20,
|
||||
InPositionOrder: 0,
|
||||
},
|
||||
},
|
||||
|
@ -1,8 +1,6 @@
|
||||
package readmodel
|
||||
|
||||
import (
|
||||
"github.com/shopspring/decimal"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/v2/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/v2/system"
|
||||
"github.com/zitadel/zitadel/internal/v2/system/mirror"
|
||||
@ -10,7 +8,7 @@ import (
|
||||
|
||||
type LastSuccessfulMirror struct {
|
||||
ID string
|
||||
Position decimal.Decimal
|
||||
Position float64
|
||||
source string
|
||||
}
|
||||
|
||||
@ -55,7 +53,7 @@ func (h *LastSuccessfulMirror) Reduce(events ...*eventstore.StorageEvent) (err e
|
||||
|
||||
func (h *LastSuccessfulMirror) reduceSucceeded(event *eventstore.StorageEvent) error {
|
||||
// if position is set we skip all older events
|
||||
if h.Position.GreaterThan(decimal.NewFromInt(0)) {
|
||||
if h.Position > 0 {
|
||||
return nil
|
||||
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
package mirror
|
||||
|
||||
import (
|
||||
"github.com/shopspring/decimal"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/v2/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
@ -11,7 +9,7 @@ type succeededPayload struct {
|
||||
// Source is the name of the database data are mirrored from
|
||||
Source string `json:"source"`
|
||||
// Position until data will be mirrored
|
||||
Position decimal.Decimal `json:"position"`
|
||||
Position float64 `json:"position"`
|
||||
}
|
||||
|
||||
const SucceededType = eventTypePrefix + "succeeded"
|
||||
@ -40,7 +38,7 @@ func SucceededEventFromStorage(event *eventstore.StorageEvent) (e *SucceededEven
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewSucceededCommand(source string, position decimal.Decimal) *eventstore.Command {
|
||||
func NewSucceededCommand(source string, position float64) *eventstore.Command {
|
||||
return &eventstore.Command{
|
||||
Action: eventstore.Action[any]{
|
||||
Creator: Creator,
|
||||
|
@ -4,43 +4,42 @@ ZITADEL_HOST ?=
|
||||
ADMIN_LOGIN_NAME ?=
|
||||
ADMIN_PASSWORD ?=
|
||||
|
||||
K6 := ./../../xk6-modules/k6
|
||||
|
||||
.PHONY: human_password_login
|
||||
human_password_login: bundle
|
||||
${K6} run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/human_password_login.js --vus ${VUS} --duration ${DURATION}
|
||||
k6 run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/human_password_login.js --vus ${VUS} --duration ${DURATION}
|
||||
|
||||
.PHONY: machine_pat_login
|
||||
machine_pat_login: bundle
|
||||
${K6} run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/machine_pat_login.js --vus ${VUS} --duration ${DURATION}
|
||||
k6 run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/machine_pat_login.js --vus ${VUS} --duration ${DURATION}
|
||||
|
||||
.PHONY: machine_client_credentials_login
|
||||
machine_client_credentials_login: bundle
|
||||
${K6} run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/machine_client_credentials_login.js --vus ${VUS} --duration ${DURATION}
|
||||
k6 run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/machine_client_credentials_login.js --vus ${VUS} --duration ${DURATION}
|
||||
|
||||
.PHONY: user_info
|
||||
user_info: bundle
|
||||
${K6} run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/user_info.js --vus ${VUS} --duration ${DURATION}
|
||||
k6 run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/user_info.js --vus ${VUS} --duration ${DURATION}
|
||||
|
||||
.PHONY: manipulate_user
|
||||
manipulate_user: bundle
|
||||
${K6} run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/manipulate_user.js --vus ${VUS} --duration ${DURATION}
|
||||
k6 run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/manipulate_user.js --vus ${VUS} --duration ${DURATION}
|
||||
|
||||
.PHONY: introspect
|
||||
introspect: ensure_modules bundle
|
||||
go install go.k6.io/xk6/cmd/xk6@latest
|
||||
cd ../../xk6-modules && xk6 build --with xk6-zitadel=.
|
||||
${K6} run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/introspection.js --vus ${VUS} --duration ${DURATION}
|
||||
./../../xk6-modules/k6 run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/introspection.js --vus ${VUS} --duration ${DURATION}
|
||||
|
||||
.PHONY: add_session
|
||||
add_session: bundle
|
||||
${K6} run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/session.js --vus ${VUS} --duration ${DURATION}
|
||||
k6 run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/session.js --vus ${VUS} --duration ${DURATION}
|
||||
|
||||
.PHONY: machine_jwt_profile_grant
|
||||
machine_jwt_profile_grant: ensure_modules ensure_key_pair bundle
|
||||
go install go.k6.io/xk6/cmd/xk6@latest
|
||||
cd ../../xk6-modules && xk6 build --with xk6-zitadel=.
|
||||
${K6} run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/machine_jwt_profile_grant.js --vus ${VUS} --duration ${DURATION}
|
||||
./../../xk6-modules/k6 run --summary-trend-stats "min,avg,max,p(50),p(95),p(99)" dist/machine_jwt_profile_grant.js --iterations 1
|
||||
# --vus ${VUS} --duration ${DURATION}
|
||||
|
||||
.PHONY: machine_jwt_profile_grant_single_user
|
||||
machine_jwt_profile_grant_single_user: ensure_modules ensure_key_pair bundle
|
||||
@ -65,8 +64,6 @@ endif
|
||||
bundle:
|
||||
npm i
|
||||
npm run bundle
|
||||
go install go.k6.io/xk6/cmd/xk6@latest
|
||||
cd ../../xk6-modules && xk6 build --with xk6-zitadel=.
|
||||
|
||||
.PHONY: ensure_key_pair
|
||||
ensure_key_pair:
|
||||
|
@ -45,4 +45,3 @@ export function teardown(data: any) {
|
||||
removeOrg(data.org, data.tokens.accessToken);
|
||||
console.info('teardown: org removed');
|
||||
}
|
||||
|
||||
|
@ -437,7 +437,7 @@ service AdminService {
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags: "SMTP";
|
||||
tags: "SMTP Configs";
|
||||
deprecated: true;
|
||||
};
|
||||
}
|
||||
@ -457,7 +457,7 @@ service AdminService {
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags: "SMTP";
|
||||
tags: "SMTP Configs";
|
||||
deprecated: true;
|
||||
};
|
||||
}
|
||||
@ -478,7 +478,7 @@ service AdminService {
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags: "SMTP";
|
||||
tags: "SMTP Configs";
|
||||
deprecated: true;
|
||||
};
|
||||
}
|
||||
@ -499,7 +499,7 @@ service AdminService {
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags: "SMTP";
|
||||
tags: "SMTP Configs";
|
||||
deprecated: true;
|
||||
};
|
||||
}
|
||||
@ -520,7 +520,7 @@ service AdminService {
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags: "SMTP";
|
||||
tags: "SMTP Configs";
|
||||
deprecated: true;
|
||||
};
|
||||
}
|
||||
@ -541,7 +541,7 @@ service AdminService {
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags: "SMTP Provider";
|
||||
tags: "SMTP Configs";
|
||||
summary: "Activate SMTP Provider";
|
||||
description: "Activate an SMTP provider."
|
||||
deprecated: true;
|
||||
@ -564,7 +564,7 @@ service AdminService {
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags: "SMTP Provider";
|
||||
tags: "SMTP Configs";
|
||||
deprecated: true;
|
||||
};
|
||||
}
|
||||
@ -584,7 +584,7 @@ service AdminService {
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags: "SMTP";
|
||||
tags: "SMTP Configs";
|
||||
deprecated: true;
|
||||
};
|
||||
}
|
||||
@ -605,7 +605,7 @@ service AdminService {
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags: "SMTP Provider";
|
||||
tags: "SMTP Configs";
|
||||
deprecated: true;
|
||||
};
|
||||
}
|
||||
@ -626,7 +626,7 @@ service AdminService {
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags: "SMTP Provider";
|
||||
tags: "SMTP Configs";
|
||||
deprecated: true;
|
||||
};
|
||||
}
|
||||
@ -663,7 +663,7 @@ service AdminService {
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags: "Email providers";
|
||||
tags: "Email Provider";
|
||||
summary: "List Email providers";
|
||||
description: "Returns a list of Email providers."
|
||||
};
|
||||
@ -679,7 +679,7 @@ service AdminService {
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags: "Email";
|
||||
tags: "Email Provider";
|
||||
summary: "Get active Email provider";
|
||||
description: "Returns the active Email provider from the system. This is used to send E-Mails to the users."
|
||||
};
|
||||
@ -695,7 +695,7 @@ service AdminService {
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags: "Email";
|
||||
tags: "Email Provider";
|
||||
summary: "Get Email provider by its id";
|
||||
description: "Get a specific Email provider by its ID.";
|
||||
};
|
||||
@ -712,7 +712,7 @@ service AdminService {
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags: "Email";
|
||||
tags: "Email Provider";
|
||||
summary: "Add SMTP Email provider";
|
||||
description: "Add a new SMTP Email provider if nothing is set yet."
|
||||
};
|
||||
@ -729,7 +729,7 @@ service AdminService {
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags: "Email";
|
||||
tags: "Email Provider";
|
||||
summary: "Update SMTP Email provider";
|
||||
description: "Update the SMTP Email provider, be aware that this will be activated as soon as it is saved. So the users will get notifications from the newly configured SMTP."
|
||||
};
|
||||
@ -746,7 +746,7 @@ service AdminService {
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags: "Email";
|
||||
tags: "Email Provider";
|
||||
summary: "Add HTTP Email provider";
|
||||
description: "Add a new HTTP Email provider if nothing is set yet."
|
||||
};
|
||||
@ -763,7 +763,7 @@ service AdminService {
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags: "Email";
|
||||
tags: "Email Provider";
|
||||
summary: "Update HTTP Email provider";
|
||||
description: "Update the HTTP Email provider, be aware that this will be activated as soon as it is saved. So the users will get notifications from the newly configured HTTP."
|
||||
};
|
||||
@ -780,7 +780,7 @@ service AdminService {
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags: "SMTP";
|
||||
tags: "Email Provider";
|
||||
summary: "Update SMTP Password";
|
||||
description: "Update the SMTP password that is used for the host, be aware that this will be activated as soon as it is saved. So the users will get notifications from the newly configured SMTP."
|
||||
};
|
||||
@ -830,7 +830,7 @@ service AdminService {
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags: "Email";
|
||||
tags: "Email Provider";
|
||||
summary: "Remove Email provider";
|
||||
description: "Remove the Email provider, be aware that the users will not get an E-Mail if no provider is set."
|
||||
};
|
||||
@ -847,7 +847,7 @@ service AdminService {
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags: "SMTP Email Provider";
|
||||
tags: "Email Provider";
|
||||
summary: "Test SMTP Email Provider";
|
||||
description: "Test an SMTP Email provider identified by its ID. After testing the provider, the users will receive information about the test results."
|
||||
};
|
||||
@ -864,7 +864,7 @@ service AdminService {
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags: "SMTP EMailProvider";
|
||||
tags: "Email Provider";
|
||||
summary: "Test SMTP Email Provider";
|
||||
description: "Test an SMTP Email provider. After testing the provider, the users will receive information about the test results."
|
||||
};
|
||||
|
@ -1565,6 +1565,11 @@ message UserGrant {
|
||||
description: "type of the user (human / machine)"
|
||||
}
|
||||
];
|
||||
zitadel.user.v1.UserGrantState state = 13 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "current state of the user grant";
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
message ListMyProjectOrgsRequest {
|
||||
|
Loading…
x
Reference in New Issue
Block a user