mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 20:37:23 +00:00
feat: support client_credentials for service users (#5134)
Request an access_token for service users with OAuth 2.0 Client Credentials Grant. Added functionality to generate and remove a secret on service users.
This commit is contained in:
parent
7c7c93117b
commit
e2fdd3f077
@ -55,6 +55,7 @@ import { PasswordComponent } from './password/password.component';
|
||||
import { PasswordlessComponent } from './user-detail/passwordless/passwordless.component';
|
||||
import { UserDetailComponent } from './user-detail/user-detail.component';
|
||||
import { UserMfaComponent } from './user-detail/user-mfa/user-mfa.component';
|
||||
import { MachineSecretDialogComponent } from './user-detail/machine-secret-dialog/machine-secret-dialog.component';
|
||||
import { MetadataModule } from 'src/app/modules/metadata/metadata.module';
|
||||
import { QRCodeModule } from 'angularx-qrcode';
|
||||
|
||||
@ -75,6 +76,7 @@ import { QRCodeModule } from 'angularx-qrcode';
|
||||
DialogU2FComponent,
|
||||
DialogPasswordlessComponent,
|
||||
AuthFactorDialogComponent,
|
||||
MachineSecretDialogComponent,
|
||||
],
|
||||
imports: [
|
||||
ChangesModule,
|
||||
|
@ -0,0 +1,50 @@
|
||||
<h1 mat-dialog-title>
|
||||
<span class="title">{{ 'USER.SECRETDIALOG.CLIENTSECRET' | translate }}</span>
|
||||
</h1>
|
||||
<p class="desc cnsl-secondary-text">{{ 'USER.SECRETDIALOG.CLIENTSECRET_DESCRIPTION' | translate }}</p>
|
||||
<div mat-dialog-content>
|
||||
<div class="flex" *ngIf="data.clientId">
|
||||
<span class="overflow-auto" data-e2e="client-id"><span class="desc">ClientId:</span> {{ data.clientId }}</span>
|
||||
<button
|
||||
color="primary"
|
||||
[disabled]="copied === data.clientId"
|
||||
matTooltip="copy to clipboard"
|
||||
cnslCopyToClipboard
|
||||
[valueToCopy]="data.clientId"
|
||||
(copiedValue)="this.copied = $event"
|
||||
mat-icon-button
|
||||
data-e2e="client-id-copy"
|
||||
>
|
||||
<i *ngIf="copied !== data.clientId" class="las la-clipboard"></i>
|
||||
<i *ngIf="copied === data.clientId" class="las la-clipboard-check"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div *ngIf="data.clientSecret" class="flex">
|
||||
<span class="overflow-auto"><span class="desc cnsl-secondary-text">ClientSecret:</span> {{ data.clientSecret }}</span>
|
||||
<button
|
||||
color="primary"
|
||||
[disabled]="copied === data.clientSecret"
|
||||
matTooltip="copy to clipboard"
|
||||
cnslCopyToClipboard
|
||||
[valueToCopy]="data.clientSecret"
|
||||
(copiedValue)="this.copied = $event"
|
||||
mat-icon-button
|
||||
>
|
||||
<i *ngIf="copied !== data.clientSecret" class="las la-clipboard"></i>
|
||||
<i *ngIf="copied === data.clientSecret" class="las la-clipboard-check"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div mat-dialog-actions class="action">
|
||||
<button
|
||||
cdkFocusInitial
|
||||
color="primary"
|
||||
mat-raised-button
|
||||
class="ok-button"
|
||||
(click)="closeDialog()"
|
||||
data-e2e="close-dialog"
|
||||
>
|
||||
{{ 'ACTIONS.CLOSE' | translate }}
|
||||
</button>
|
||||
</div>
|
@ -0,0 +1,37 @@
|
||||
.title {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.action {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
.ok-button {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1px solid #ffffff20;
|
||||
border-radius: 0.5rem;
|
||||
padding-left: 0.5rem;
|
||||
justify-content: space-between;
|
||||
|
||||
.overflow-auto {
|
||||
overflow: auto;
|
||||
|
||||
.desc {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { MachineSecretDialogComponent } from './machine-secret-dialog.component';
|
||||
|
||||
describe('MachineSecretDialogComponent', () => {
|
||||
let component: MachineSecretDialogComponent;
|
||||
let fixture: ComponentFixture<MachineSecretDialogComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [MachineSecretDialogComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(MachineSecretDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,19 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import {
|
||||
MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA,
|
||||
MatLegacyDialogRef as MatDialogRef,
|
||||
} from '@angular/material/legacy-dialog';
|
||||
|
||||
@Component({
|
||||
selector: 'cnsl-machine-secret-dialog',
|
||||
templateUrl: './machine-secret-dialog.component.html',
|
||||
styleUrls: ['./machine-secret-dialog.component.scss'],
|
||||
})
|
||||
export class MachineSecretDialogComponent {
|
||||
public copied: string = '';
|
||||
constructor(public dialogRef: MatDialogRef<MachineSecretDialogComponent>, @Inject(MAT_DIALOG_DATA) public data: any) {}
|
||||
|
||||
public closeDialog(): void {
|
||||
this.dialogRef.close(false);
|
||||
}
|
||||
}
|
@ -10,6 +10,12 @@
|
||||
[hasActions]="['user.write$', 'user.write:' + user.id] | hasRole | async"
|
||||
>
|
||||
<ng-template topActions cnslHasRole [hasRole]="['user.write$', 'user.write:' + user.id]">
|
||||
<button mat-menu-item color="warn" *ngIf="user?.machine" (click)="generateMachineSecret()">
|
||||
{{ 'USER.PAGES.GENERATESECRET' | translate }}
|
||||
</button>
|
||||
<button mat-menu-item color="warn" *ngIf="user?.machine?.hasSecret" (click)="removeMachineSecret()">
|
||||
{{ 'USER.PAGES.REMOVESECRET' | translate }}
|
||||
</button>
|
||||
<button mat-menu-item color="warn" *ngIf="user?.state === UserState.USER_STATE_LOCKED" (click)="unlockUser()">
|
||||
{{ 'USER.PAGES.UNLOCK' | translate }}
|
||||
</button>
|
||||
|
@ -21,6 +21,7 @@ import { Buffer } from 'buffer';
|
||||
import { EditDialogComponent, EditDialogType } from '../auth-user-detail/edit-dialog/edit-dialog.component';
|
||||
import { ResendEmailDialogComponent } from '../auth-user-detail/resend-email-dialog/resend-email-dialog.component';
|
||||
import { LoginPolicy } from 'src/app/proto/generated/zitadel/policy_pb';
|
||||
import { MachineSecretDialogComponent } from './machine-secret-dialog/machine-secret-dialog.component';
|
||||
|
||||
const GENERAL: SidenavSetting = { id: 'general', i18nKey: 'USER.SETTINGS.GENERAL' };
|
||||
const GRANTS: SidenavSetting = { id: 'grants', i18nKey: 'USER.SETTINGS.USERGRANTS' };
|
||||
@ -189,6 +190,38 @@ export class UserDetailComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
public generateMachineSecret(): void {
|
||||
this.mgmtUserService
|
||||
.generateMachineSecret(this.user.id)
|
||||
.then((resp) => {
|
||||
this.toast.showInfo('USER.TOAST.SECRETGENERATED', true);
|
||||
console.log(resp.clientSecret);
|
||||
this.dialog.open(MachineSecretDialogComponent, {
|
||||
data: {
|
||||
clientId: resp.clientId,
|
||||
clientSecret: resp.clientSecret,
|
||||
},
|
||||
width: '400px',
|
||||
});
|
||||
this.refreshUser();
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
|
||||
public removeMachineSecret(): void {
|
||||
this.mgmtUserService
|
||||
.removeMachineSecret(this.user.id)
|
||||
.then((resp) => {
|
||||
this.toast.showInfo('USER.TOAST.SECRETREMOVED', true);
|
||||
this.refreshUser();
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
|
||||
public changeState(newState: UserState): void {
|
||||
if (newState === UserState.USER_STATE_ACTIVE) {
|
||||
this.mgmtUserService
|
||||
|
@ -98,6 +98,8 @@ import {
|
||||
DeactivateUserResponse,
|
||||
DeleteActionRequest,
|
||||
DeleteActionResponse,
|
||||
GenerateMachineSecretRequest,
|
||||
GenerateMachineSecretResponse,
|
||||
GenerateOrgDomainValidationRequest,
|
||||
GenerateOrgDomainValidationResponse,
|
||||
GetActionRequest,
|
||||
@ -310,6 +312,8 @@ import {
|
||||
RemoveIDPFromLoginPolicyResponse,
|
||||
RemoveMachineKeyRequest,
|
||||
RemoveMachineKeyResponse,
|
||||
RemoveMachineSecretRequest,
|
||||
RemoveMachineSecretResponse,
|
||||
RemoveMultiFactorFromLoginPolicyRequest,
|
||||
RemoveMultiFactorFromLoginPolicyResponse,
|
||||
RemoveOrgDomainRequest,
|
||||
@ -717,6 +721,18 @@ export class ManagementService {
|
||||
return this.grpcService.mgmt.unlockUser(req, null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public generateMachineSecret(userId: string): Promise<GenerateMachineSecretResponse.AsObject> {
|
||||
const req = new GenerateMachineSecretRequest();
|
||||
req.setUserId(userId);
|
||||
return this.grpcService.mgmt.generateMachineSecret(req, null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public removeMachineSecret(userId: string): Promise<RemoveMachineSecretResponse.AsObject> {
|
||||
const req = new RemoveMachineSecretRequest();
|
||||
req.setUserId(userId);
|
||||
return this.grpcService.mgmt.removeMachineSecret(req, null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public getPrivacyPolicy(): Promise<GetPrivacyPolicyResponse.AsObject> {
|
||||
const req = new GetPrivacyPolicyRequest();
|
||||
return this.grpcService.mgmt.getPrivacyPolicy(req, null).then((resp) => resp.toObject());
|
||||
|
@ -240,6 +240,8 @@
|
||||
"STATE": "Status",
|
||||
"DELETE": "Benutzer löschen",
|
||||
"UNLOCK": "Benutzer entsperren",
|
||||
"GENERATESECRET": "Client Secret generieren",
|
||||
"REMOVESECRET": "Client Secret löschen",
|
||||
"LOCKEDDESCRIPTION": "Dieser Benutzer wurde aufgrund der Überschreitung der maximalen Anmeldeversuche gesperrt und muss zur erneuten Verwendung entsperrt werden.",
|
||||
"DELETEACCOUNT": "Account löschen",
|
||||
"DELETEACCOUNT_DESC": "Wenn du diese Aktion ausführst, wirst du abgemeldet und danach keinen Zugriff mehr auf dein Konto haben. Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||
@ -265,6 +267,10 @@
|
||||
"DESCRIPTION": "Klicken Sie den untenstehenden Button um ein Verifizierung-E-Mail an die aktuelle Adresse zu versenden oder ändern Sie die Emailadresse in dem Feld.",
|
||||
"NEWEMAIL": "Neue Email"
|
||||
},
|
||||
"SECRETDIALOG": {
|
||||
"CLIENTSECRET": "Client Secret",
|
||||
"CLIENTSECRET_DESCRIPTION": "Verwahre das Client Secret an einem sicheren Ort, da es nicht mehr angezeigt werden kann, sobald der Dialog geschlossen wird."
|
||||
},
|
||||
"TABLE": {
|
||||
"DEACTIVATE": "Deaktivieren",
|
||||
"ACTIVATE": "Aktivieren",
|
||||
@ -589,7 +595,9 @@
|
||||
"MACHINEADDED": "Service User erstellt!",
|
||||
"DELETED": "Benutzer erfolgreich gelöscht!",
|
||||
"UNLOCKED": "Benutzer erfolgreich freigeschaltet!",
|
||||
"PASSWORDLESSREGISTRATIONSENT": "Link via email versendet."
|
||||
"PASSWORDLESSREGISTRATIONSENT": "Link via email versendet.",
|
||||
"SECRETGENERATED": "Secret erfolgreich generiert!",
|
||||
"SECRETREMOVED": "Secret erfolgreich gelöscht!"
|
||||
},
|
||||
"MEMBERSHIPS": {
|
||||
"TITLE": "ZITADEL Manager-Rollen",
|
||||
|
@ -240,6 +240,8 @@
|
||||
"STATE": "Status",
|
||||
"DELETE": "Delete User",
|
||||
"UNLOCK": "Unlock User",
|
||||
"GENERATESECRET": "Generate Client Secret",
|
||||
"REMOVESECRET": "Remove Client Secret",
|
||||
"LOCKEDDESCRIPTION": "This user has been locked out due to exceeding the maximum login attempts and must be unlocked to be used again.",
|
||||
"DELETEACCOUNT": "Delete Account",
|
||||
"DELETEACCOUNT_DESC": "If you perform this action, you will be logged out and will no longer have access to your account. This action is not reversible, so please continue with caution.",
|
||||
@ -265,6 +267,10 @@
|
||||
"DESCRIPTION": "Click the button below to send a notification to the current email address or change the email address in the field.",
|
||||
"NEWEMAIL": "New email address"
|
||||
},
|
||||
"SECRETDIALOG": {
|
||||
"CLIENTSECRET": "Client Secret",
|
||||
"CLIENTSECRET_DESCRIPTION": "Keep your client secret at a safe place as it will disappear once the dialog is closed."
|
||||
},
|
||||
"TABLE": {
|
||||
"DEACTIVATE": "Deactivate",
|
||||
"ACTIVATE": "Activate",
|
||||
@ -589,7 +595,9 @@
|
||||
"MACHINEADDED": "Service User created!",
|
||||
"DELETED": "User deleted successfully!",
|
||||
"UNLOCKED": "User unlocked successfully!",
|
||||
"PASSWORDLESSREGISTRATIONSENT": "Registration Link sent successfully."
|
||||
"PASSWORDLESSREGISTRATIONSENT": "Registration Link sent successfully.",
|
||||
"SECRETGENERATED": "Secret generated successfully!",
|
||||
"SECRETREMOVED": "Secret removed successfully!"
|
||||
},
|
||||
"MEMBERSHIPS": {
|
||||
"TITLE": "ZITADEL Manager Roles",
|
||||
|
@ -240,6 +240,8 @@
|
||||
"STATE": "Statut",
|
||||
"DELETE": "Supprimer l'utilisateur",
|
||||
"UNLOCK": "Déverrouiller l'utilisateur",
|
||||
"GENERATESECRET": "Générer Client Secret",
|
||||
"REMOVESECRET": "Supprimer Client Secret",
|
||||
"LOCKEDDESCRIPTION": "Cet utilisateur a été verrouillé pour avoir dépassé le nombre maximum de tentatives de connexion et doit être déverrouillé pour être à nouveau utilisé.",
|
||||
"DELETEACCOUNT": "Supprimer le compte",
|
||||
"DELETEACCOUNT_DESC": "Si vous effectuez cette action, vous serez déconnecté et n'aurez plus accès à votre compte. Cette action n'est pas réversible, veuillez donc continuer avec prudence.",
|
||||
@ -265,6 +267,10 @@
|
||||
"DESCRIPTION": "Cliquez sur le bouton ci-dessous pour envoyer une notification à l'adresse e-mail actuelle ou modifier l'adresse e-mail dans le champ.",
|
||||
"NEWEMAIL": "Nouvelle adresse e-mail"
|
||||
},
|
||||
"SECRETDIALOG": {
|
||||
"CLIENTSECRET": "Client Secret",
|
||||
"CLIENTSECRET_DESCRIPTION": "Conservez votre secret client dans un endroit sûr car il disparaîtra une fois la boîte de dialogue fermée."
|
||||
},
|
||||
"TABLE": {
|
||||
"DEACTIVATE": "Désactiver",
|
||||
"ACTIVATE": "Activer",
|
||||
@ -589,7 +595,9 @@
|
||||
"MACHINEADDED": "Utilisateur de service créé !",
|
||||
"DELETED": "Utilisateur supprimé avec succès !",
|
||||
"UNLOCKED": "Utilisateur déverrouillé avec succès !",
|
||||
"PASSWORDLESSREGISTRATIONSENT": "Lien d'enregistrement envoyé avec succès."
|
||||
"PASSWORDLESSREGISTRATIONSENT": "Lien d'enregistrement envoyé avec succès.",
|
||||
"SECRETGENERATED": "Secret généré avec succès !",
|
||||
"SECRETREMOVED": "Secret supprimé avec succès !"
|
||||
},
|
||||
"MEMBERSHIPS": {
|
||||
"TITLE": "Rôles du gestionnaire ZITADEL",
|
||||
|
@ -240,6 +240,8 @@
|
||||
"STATE": "Stato",
|
||||
"DELETE": "Elimina utente",
|
||||
"UNLOCK": "Sblocca utente",
|
||||
"GENERATESECRET": "Genera Client Secret",
|
||||
"REMOVESECRET": "Elimina Client Secret",
|
||||
"LOCKEDDESCRIPTION": "Questo utente \u00e8 stato bloccato a causa del superamento dei tentativi massimi di accesso e deve essere sbloccato per essere utilizzato di nuovo.",
|
||||
"DELETEACCOUNT": "Elimina account personale",
|
||||
"DELETEACCOUNT_DESC": "Se esegui questa azione, sarai disconnesso e non avrai più accesso al tuo account. Questa azione non può essere invertita.",
|
||||
@ -265,6 +267,10 @@
|
||||
"DESCRIPTION": "Clicca il pulsante qui sotto per inviare una notifica all'indirizzo email corrente o cambiare l'indirizzo email nel campo.",
|
||||
"NEWEMAIL": "Nuovo indirizzo e-mail"
|
||||
},
|
||||
"SECRETDIALOG": {
|
||||
"CLIENTSECRET": "Client Secret",
|
||||
"CLIENTSECRET_DESCRIPTION": "Salvate il Client Secret in un luogo sicuro, perch\u00e9 non sarà più disponibile dopo aver chiuso la finestra di dialogo"
|
||||
},
|
||||
"TABLE": {
|
||||
"DEACTIVATE": "Disattiva",
|
||||
"ACTIVATE": "Attiva",
|
||||
@ -589,7 +595,9 @@
|
||||
"MACHINEADDED": "Utente di servizio creato!",
|
||||
"DELETED": "Utente cancellato con successo!",
|
||||
"UNLOCKED": "Utente sbloccato con successo!",
|
||||
"PASSWORDLESSREGISTRATIONSENT": "Link per la registrazione inviato con successo."
|
||||
"PASSWORDLESSREGISTRATIONSENT": "Link per la registrazione inviato con successo.",
|
||||
"SECRETGENERATED": "Secret generato con successo!",
|
||||
"SECRETREMOVED": "Secret rimosso con successo!"
|
||||
},
|
||||
"MEMBERSHIPS": {
|
||||
"TITLE": "Memberships di ZITADEL",
|
||||
|
@ -240,6 +240,8 @@
|
||||
"STATE": "状态",
|
||||
"DELETE": "删除用户",
|
||||
"UNLOCK": "解锁用户",
|
||||
"GENERATESECRET": "生成客户密匙",
|
||||
"REMOVESECRET": "删除客户密匙",
|
||||
"LOCKEDDESCRIPTION": "此用户因超过最大登录尝试次数而被锁定,必须解锁才能再次使用。",
|
||||
"DELETEACCOUNT": "删除账户",
|
||||
"DELETEACCOUNT_DESC": "如果您执行此操作,您将被注销并且无法再访问您的帐户。此操作不可逆,因此请谨慎操作。",
|
||||
@ -265,6 +267,10 @@
|
||||
"DESCRIPTION": "单击下面的按钮可向当前电子邮件地址发送通知或更改电子邮件地址。",
|
||||
"NEWEMAIL": "新的电子邮件地址"
|
||||
},
|
||||
"SECRETDIALOG": {
|
||||
"CLIENTSECRET": "客户端秘钥",
|
||||
"CLIENTSECRET_DESCRIPTION": "将您的客户保密在一个安全的地方,因为一旦对话框关闭,便无法再次查看。"
|
||||
},
|
||||
"TABLE": {
|
||||
"DEACTIVATE": "停用",
|
||||
"ACTIVATE": "启用",
|
||||
@ -589,7 +595,9 @@
|
||||
"MACHINEADDED": "服务用户已创建成功!",
|
||||
"DELETED": "用户删除成功!",
|
||||
"UNLOCKED": "用户解锁成功!",
|
||||
"PASSWORDLESSREGISTRATIONSENT": "注册链接发送成功。"
|
||||
"PASSWORDLESSREGISTRATIONSENT": "注册链接发送成功。",
|
||||
"SECRETGENERATED": "秘密成功生成!",
|
||||
"SECRETREMOVED": "秘密被成功删除!"
|
||||
},
|
||||
"MEMBERSHIPS": {
|
||||
"TITLE": "CITADEL 管理角色",
|
||||
|
@ -325,6 +325,53 @@ Send a `client_assertion` as JWT for us to validate the signature against the re
|
||||
| refresh_token | An new opaque refresh_token. |
|
||||
| token_type | Type of the `access_token`. Value is always `Bearer` |
|
||||
|
||||
### Client Credentials Grant
|
||||
|
||||
#### Required request Parameters
|
||||
|
||||
| Parameter | Description |
|
||||
| ---------- | ----------------------------------------------------------------------------------------------------------------------- |
|
||||
| grant_type | Must be `client_credentials` |
|
||||
| scope | [Scopes](scopes) you would like to request from ZITADEL. Scopes are space delimited, e.g. `openid profile` |
|
||||
|
||||
Additionally, you need to authenticate your client by either sending `client_id` and `client_secret` as Basic Auth Header.
|
||||
Check [Client Secret Basic Auth Method](authn-methods#client-secret-basic) on how to build it correctly.
|
||||
|
||||
```BASH
|
||||
curl --request POST \
|
||||
--url {your_domain}/oauth/v2/token \
|
||||
--header 'Content-Type: application/x-www-form-urlencoded' \
|
||||
--header 'Authorization: Basic ${BASIC_AUTH}' \
|
||||
--data grant_type=client_credentials \
|
||||
--data scope=openid profile
|
||||
```
|
||||
|
||||
Or you can also send your `client_id` and `client_secret` as parameters in the body:
|
||||
|
||||
| Parameter | Description |
|
||||
| ------------- | -------------------------------- |
|
||||
| client_id | client_id of the application |
|
||||
| client_secret | client_secret of the application |
|
||||
|
||||
```BASH
|
||||
curl --request POST \
|
||||
--url {your_domain}/oauth/v2/token \
|
||||
--header 'Content-Type: application/x-www-form-urlencoded' \
|
||||
--data grant_type=client_credentials \
|
||||
--data client_id=${CLIENT_ID} \
|
||||
--data client_secret=${CLIENT_SECRET} \
|
||||
--data scope=openid profile
|
||||
```
|
||||
|
||||
#### Successful Client Credentials response {#token-client-credentials-response}
|
||||
|
||||
| Property | Description |
|
||||
| ------------ | ------------------------------------------------------------------------------------- |
|
||||
| access_token | An `access_token` as JWT or opaque token |
|
||||
| expires_in | Number of second until the expiration of the `access_token` |
|
||||
| scope | Scopes of the `access_token`. These might differ from the provided `scope` parameter. |
|
||||
| token_type | Type of the `access_token`. Value is always `Bearer` |
|
||||
|
||||
### Error response
|
||||
|
||||
| error_type | Possible reason |
|
||||
|
@ -8,14 +8,14 @@ For a list of supported or unsupported `Grant Types` please have a look at the t
|
||||
|:------------------------------------------------------|:--------------------|
|
||||
| Authorization Code | yes |
|
||||
| Authorization Code with PKCE | yes |
|
||||
| Client Credentials | no |
|
||||
| Client Credentials | yes |
|
||||
| Device Authorization | under consideration |
|
||||
| Implicit | yes |
|
||||
| JSON Web Token (JWT) Profile | yes |
|
||||
| Refresh Token | yes |
|
||||
| Resource Owner Password Credentials | no |
|
||||
| Security Assertion Markup Language (SAML) 2.0 Profile | no |
|
||||
| Token Exchange | no |
|
||||
| Security Assertion Markup Language (SAML) 2.0 Profile | no |
|
||||
| Token Exchange | no |
|
||||
|
||||
## Authorization Code
|
||||
|
||||
@ -131,4 +131,4 @@ Find out how to use it on the [token endpoint](endpoints#token_endpoint) or the
|
||||
|
||||
> Due to growing security concerns we do not support this grant type. With OAuth 2.1 it looks like this grant will be removed.
|
||||
|
||||
**Link to spec.** [OThe OAuth 2.0 Authorization Framework Section 1.3.3](https://tools.ietf.org/html/rfc6749#section-1.3.3)
|
||||
**Link to spec.** [OThe OAuth 2.0 Authorization Framework Section 1.3.3](https://tools.ietf.org/html/rfc6749#section-1.3.3)
|
||||
|
@ -581,6 +581,30 @@ Changes a machine user
|
||||
PUT: /users/{user_id}/machine
|
||||
|
||||
|
||||
### GenerateMachineSecret
|
||||
|
||||
> **rpc** GenerateMachineSecret([GenerateMachineSecretRequest](#generatemachinesecretrequest))
|
||||
[GenerateMachineSecretResponse](#generatemachinesecretresponse)
|
||||
|
||||
Generates and sets a new machine secret
|
||||
|
||||
|
||||
|
||||
PUT: /users/{user_id}/secret
|
||||
|
||||
|
||||
### RemoveMachineSecret
|
||||
|
||||
> **rpc** RemoveMachineSecret([RemoveMachineSecretRequest](#removemachinesecretrequest))
|
||||
[RemoveMachineSecretResponse](#removemachinesecretresponse)
|
||||
|
||||
Removes the machine secret
|
||||
|
||||
|
||||
|
||||
DELETE: /users/{user_id}/secret
|
||||
|
||||
|
||||
### GetMachineKeyByIDs
|
||||
|
||||
> **rpc** GetMachineKeyByIDs([GetMachineKeyByIDsRequest](#getmachinekeybyidsrequest))
|
||||
@ -4425,6 +4449,30 @@ This is an empty request
|
||||
|
||||
|
||||
|
||||
### GenerateMachineSecretRequest
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| user_id | string | - | string.min_len: 1<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### GenerateMachineSecretResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| client_id | string | - | |
|
||||
| client_secret | string | - | |
|
||||
| details | zitadel.v1.ObjectDetails | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### GenerateOrgDomainValidationRequest
|
||||
|
||||
|
||||
@ -7175,6 +7223,28 @@ This is an empty request
|
||||
|
||||
|
||||
|
||||
### RemoveMachineSecretRequest
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| user_id | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### RemoveMachineSecretResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| details | zitadel.v1.ObjectDetails | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### RemoveMultiFactorFromLoginPolicyRequest
|
||||
|
||||
|
||||
|
@ -133,6 +133,7 @@ title: zitadel/user.proto
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| name | string | - | |
|
||||
| description | string | - | |
|
||||
| has_secret | bool | - | |
|
||||
|
||||
|
||||
|
||||
|
88
docs/docs/guides/integrate/client-credentials.md
Normal file
88
docs/docs/guides/integrate/client-credentials.md
Normal file
@ -0,0 +1,88 @@
|
||||
---
|
||||
title: Client Credentials with Service Users
|
||||
---
|
||||
|
||||
This is a guide on how to use Client Credentials with service users in ZITADEL. You can read more about users [here](/concepts/structure/users.md).
|
||||
|
||||
In ZITADEL, the Client Credentials grant can be used for this non-interactive authentication as alternative to the [JWT profile authentication](serviceusers).
|
||||
|
||||
## Create a Service User with a Secret
|
||||
|
||||
1. Navigate to Service Users
|
||||
2. Click on **New**
|
||||
3. Enter a username and a display name
|
||||
4. Click on **Create**
|
||||
5. Open **Actions** in the top right corner and click on **Generate Client Secret**
|
||||
6. Copy the **ClientID** and **ClientSecret** from the dialog
|
||||
|
||||
:::note
|
||||
Be sure to copy in particular the ClientSecret. You won't be able to retrieve it again.
|
||||
If you lose it, you will have to generate a new one.
|
||||
:::
|
||||
|
||||

|
||||
|
||||
## Grant role for ZITADEL
|
||||
|
||||
To be able to access the ZITADEL APIs your service user needs permissions to ZITADEL.
|
||||
|
||||
1. Go to the detail page of your organization
|
||||
2. Click in the top right corner the "+" button
|
||||
3. Search for your service user
|
||||
4. Give the user the role you need, for the example we choose Org Owner (More about [ZITADEL Permissions](../manage/console/managers))
|
||||
|
||||

|
||||
|
||||
## Authenticating a service user
|
||||
|
||||
In this step we will authenticate a service user and receive an access_token to use against the ZITADEL API.
|
||||
|
||||
You will need to craft a POST request to ZITADEL's token endpoint:
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url https://{your_domain}.zitadel.cloud/oauth/v2/token \
|
||||
--header 'Content-Type: application/x-www-form-urlencoded' \
|
||||
--header 'Authorization: Basic ${BASIC_AUTH}' \
|
||||
--data grant_type=client_credentials \
|
||||
--data scope='openid profile email urn:zitadel:iam:org:project:id:zitadel:aud'
|
||||
```
|
||||
|
||||
* `grant_type` should be set to `client_credentials`
|
||||
* `scope` should contain any [Scopes](../../apis/openidoauth/scopes) you want to include, but must include `openid`. For this example, please include `profile`, `email`
|
||||
and `urn:zitadel:iam:org:project:id:zitadel:aud`. The latter provides access to the ZITADEL API.
|
||||
|
||||
You should receive a successful response with `access_token`, `token_type` and time to expiry in seconds as `expires_in`.
|
||||
|
||||
```bash
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"access_token": "MtjHodGy4zxKylDOhg6kW90WeEQs2q...",
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 43199
|
||||
}
|
||||
```
|
||||
|
||||
## Call ZITADEL API with Token
|
||||
|
||||
Because the received Token includes the `urn:zitadel:iam:org:project:id:zitadel:aud` scope, we can send it in your requests to the ZITADEL API as Authorization Header.
|
||||
In this example we read the organization of the service user.
|
||||
|
||||
```bash
|
||||
curl --request GET \
|
||||
--url {your-domain}/management/v1/orgs/me \
|
||||
--header 'Authorization: Bearer ${TOKEN}'
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
* With service users you can secure machine-to-machine communication
|
||||
* Client Credentials provide an alternative way to JWT Profile for service user authentication
|
||||
* After successful authorization you can use an access token like for human users
|
||||
|
||||
Where to go from here:
|
||||
|
||||
* Management API
|
||||
* Securing backend API
|
@ -109,6 +109,7 @@ module.exports = {
|
||||
items: [
|
||||
"guides/integrate/serviceusers",
|
||||
"guides/integrate/access-zitadel-apis",
|
||||
"guides/integrate/client-credentials",
|
||||
"guides/integrate/pat",
|
||||
"guides/integrate/access-zitadel-system-api",
|
||||
"guides/integrate/export-and-import",
|
||||
|
BIN
docs/static/img/console_serviceusers_secret.gif
vendored
Normal file
BIN
docs/static/img/console_serviceusers_secret.gif
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.9 MiB |
2
go.mod
2
go.mod
@ -55,7 +55,7 @@ require (
|
||||
github.com/superseriousbusiness/exifremove v0.0.0-20210330092427-6acd27eac203
|
||||
github.com/ttacon/libphonenumber v1.2.1
|
||||
github.com/zitadel/logging v0.3.4
|
||||
github.com/zitadel/oidc/v2 v2.0.0-dynamic-issuer.6
|
||||
github.com/zitadel/oidc/v2 v2.0.0-dynamic-issuer.7
|
||||
github.com/zitadel/saml v0.0.9
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.27.0
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.27.0
|
||||
|
4
go.sum
4
go.sum
@ -906,8 +906,8 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
github.com/zitadel/logging v0.3.4 h1:9hZsTjMMTE3X2LUi0xcF9Q9EdLo+FAezeu52ireBbHM=
|
||||
github.com/zitadel/logging v0.3.4/go.mod h1:aPpLQhE+v6ocNK0TWrBrd363hZ95KcI17Q1ixAQwZF0=
|
||||
github.com/zitadel/oidc/v2 v2.0.0-dynamic-issuer.6 h1:DGTEizuL1npVfmw+i6lFWxrEdKNUjEFpqGEAZH7amfo=
|
||||
github.com/zitadel/oidc/v2 v2.0.0-dynamic-issuer.6/go.mod h1:2jHMP6o/WK0EmcNJkz+FSpjeqcCuQG9YqqqzKZkfgIE=
|
||||
github.com/zitadel/oidc/v2 v2.0.0-dynamic-issuer.7 h1:CGs4gdoSrZZyZM5pGeXCf8FH12r4r8hpJL/wUR3PxRA=
|
||||
github.com/zitadel/oidc/v2 v2.0.0-dynamic-issuer.7/go.mod h1:2jHMP6o/WK0EmcNJkz+FSpjeqcCuQG9YqqqzKZkfgIE=
|
||||
github.com/zitadel/saml v0.0.9 h1:q7FRu52Wm2S5rsSGuzR2nYhEClvexga8bwnGrBL7Bbw=
|
||||
github.com/zitadel/saml v0.0.9/go.mod h1:DIy/ln32rNYv/bIBA8uOB6Y2JmxjZldDYBeMNn7YyeQ=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
|
@ -756,6 +756,34 @@ func (s *Server) RemoveMachineKey(ctx context.Context, req *mgmt_pb.RemoveMachin
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GenerateMachineSecret(ctx context.Context, req *mgmt_pb.GenerateMachineSecretRequest) (*mgmt_pb.GenerateMachineSecretResponse, error) {
|
||||
// use SecretGeneratorTypeAppSecret as the secrets will be used in the client_credentials grant like a client secret
|
||||
secretGenerator, err := s.query.InitHashGenerator(ctx, domain.SecretGeneratorTypeAppSecret, s.passwordHashAlg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
set := new(command.GenerateMachineSecret)
|
||||
details, err := s.command.GenerateMachineSecret(ctx, req.UserId, authz.GetCtxData(ctx).OrgID, secretGenerator, set)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.GenerateMachineSecretResponse{
|
||||
ClientId: set.ClientID,
|
||||
ClientSecret: set.ClientSecret,
|
||||
Details: obj_grpc.DomainToAddDetailsPb(details),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) RemoveMachineSecret(ctx context.Context, req *mgmt_pb.RemoveMachineSecretRequest) (*mgmt_pb.RemoveMachineSecretResponse, error) {
|
||||
objectDetails, err := s.command.RemoveMachineSecret(ctx, req.UserId, authz.GetCtxData(ctx).OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.RemoveMachineSecretResponse{
|
||||
Details: obj_grpc.DomainToChangeDetailsPb(objectDetails),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetPersonalAccessTokenByIDs(ctx context.Context, req *mgmt_pb.GetPersonalAccessTokenByIDsRequest) (*mgmt_pb.GetPersonalAccessTokenByIDsResponse, error) {
|
||||
resourceOwner, err := query.NewPersonalAccessTokenResourceOwnerSearchQuery(authz.GetCtxData(ctx).OrgID)
|
||||
if err != nil {
|
||||
|
@ -72,6 +72,7 @@ func MachineToPb(view *query.Machine) *user_pb.Machine {
|
||||
return &user_pb.Machine{
|
||||
Name: view.Name,
|
||||
Description: view.Description,
|
||||
HasSecret: view.HasSecret,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -94,29 +94,7 @@ func (o *OPStorage) ValidateJWTProfileScopes(ctx context.Context, subject string
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := len(scopes) - 1; i >= 0; i-- {
|
||||
scope := scopes[i]
|
||||
if strings.HasPrefix(scope, domain.OrgDomainPrimaryScope) {
|
||||
var orgID string
|
||||
org, err := o.query.OrgByPrimaryDomain(ctx, strings.TrimPrefix(scope, domain.OrgDomainPrimaryScope))
|
||||
if err == nil {
|
||||
orgID = org.ID
|
||||
}
|
||||
if orgID != user.ResourceOwner {
|
||||
scopes[i] = scopes[len(scopes)-1]
|
||||
scopes[len(scopes)-1] = ""
|
||||
scopes = scopes[:len(scopes)-1]
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(scope, domain.OrgIDScope) {
|
||||
if strings.TrimPrefix(scope, domain.OrgIDScope) != user.ResourceOwner {
|
||||
scopes[i] = scopes[len(scopes)-1]
|
||||
scopes[len(scopes)-1] = ""
|
||||
scopes = scopes[:len(scopes)-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
return scopes, nil
|
||||
return o.checkOrgScopes(ctx, user, scopes)
|
||||
}
|
||||
|
||||
func (o *OPStorage) AuthorizeClientIDSecret(ctx context.Context, id string, secret string) (err error) {
|
||||
@ -209,6 +187,68 @@ func (o *OPStorage) SetIntrospectionFromToken(ctx context.Context, introspection
|
||||
return errors.ThrowPermissionDenied(nil, "OIDC-sdg3G", "token is not valid for this client")
|
||||
}
|
||||
|
||||
func (o *OPStorage) ClientCredentialsTokenRequest(ctx context.Context, clientID string, scope []string) (op.TokenRequest, error) {
|
||||
loginname, err := query.NewUserLoginNamesSearchQuery(clientID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user, err := o.query.GetUser(ctx, false, false, loginname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
scope, err = o.checkOrgScopes(ctx, user, scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &clientCredentialsRequest{
|
||||
sub: user.ID,
|
||||
scopes: scope,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (o *OPStorage) ClientCredentials(ctx context.Context, clientID, clientSecret string) (op.Client, error) {
|
||||
loginname, err := query.NewUserLoginNamesSearchQuery(clientID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user, err := o.query.GetUser(ctx, false, false, loginname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := o.command.VerifyMachineSecret(ctx, user.ID, user.ResourceOwner, clientSecret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &clientCredentialsClient{
|
||||
id: clientID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (o *OPStorage) checkOrgScopes(ctx context.Context, user *query.User, scopes []string) ([]string, error) {
|
||||
for i := len(scopes) - 1; i >= 0; i-- {
|
||||
scope := scopes[i]
|
||||
if strings.HasPrefix(scope, domain.OrgDomainPrimaryScope) {
|
||||
var orgID string
|
||||
org, err := o.query.OrgByPrimaryDomain(ctx, strings.TrimPrefix(scope, domain.OrgDomainPrimaryScope))
|
||||
if err == nil {
|
||||
orgID = org.ID
|
||||
}
|
||||
if orgID != user.ResourceOwner {
|
||||
scopes[i] = scopes[len(scopes)-1]
|
||||
scopes[len(scopes)-1] = ""
|
||||
scopes = scopes[:len(scopes)-1]
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(scope, domain.OrgIDScope) {
|
||||
if strings.TrimPrefix(scope, domain.OrgIDScope) != user.ResourceOwner {
|
||||
scopes[i] = scopes[len(scopes)-1]
|
||||
scopes[len(scopes)-1] = ""
|
||||
scopes = scopes[:len(scopes)-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
return scopes, nil
|
||||
}
|
||||
|
||||
func (o *OPStorage) setUserinfo(ctx context.Context, userInfo oidc.UserInfoSetter, userID, applicationID string, scopes []string) (err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
120
internal/api/oidc/client_credentials.go
Normal file
120
internal/api/oidc/client_credentials.go
Normal file
@ -0,0 +1,120 @@
|
||||
package oidc
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v2/pkg/op"
|
||||
)
|
||||
|
||||
type clientCredentialsRequest struct {
|
||||
sub string
|
||||
scopes []string
|
||||
}
|
||||
|
||||
func (c *clientCredentialsRequest) GetSubject() string {
|
||||
return c.sub
|
||||
}
|
||||
|
||||
// GetAudience returns the audience for token to be created because of the client credentials request
|
||||
// return nil as the audience is set during the token creation in command.addUserToken
|
||||
func (c *clientCredentialsRequest) GetAudience() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *clientCredentialsRequest) GetScopes() []string {
|
||||
return c.scopes
|
||||
}
|
||||
|
||||
type clientCredentialsClient struct {
|
||||
id string
|
||||
}
|
||||
|
||||
// AccessTokenType returns the AccessTokenType for the token to be created because of the client credentials request
|
||||
// machine users currently only have opaque tokens ([op.AccessTokenTypeBearer])
|
||||
func (c *clientCredentialsClient) AccessTokenType() op.AccessTokenType {
|
||||
return op.AccessTokenTypeBearer
|
||||
}
|
||||
|
||||
// GetID returns the client_id (username of the machine user) for the token to be created because of the client credentials request
|
||||
func (c *clientCredentialsClient) GetID() string {
|
||||
return c.id
|
||||
}
|
||||
|
||||
// RedirectURIs returns nil as there are no redirect uris
|
||||
func (c *clientCredentialsClient) RedirectURIs() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// PostLogoutRedirectURIs returns nil as there are no logout redirect uris
|
||||
func (c *clientCredentialsClient) PostLogoutRedirectURIs() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplicationType returns [op.ApplicationTypeWeb] as the machine users is a confidential client
|
||||
func (c *clientCredentialsClient) ApplicationType() op.ApplicationType {
|
||||
return op.ApplicationTypeWeb
|
||||
}
|
||||
|
||||
// AuthMethod returns the allowed auth method type for machine user.
|
||||
// It returns Basic Auth
|
||||
func (c *clientCredentialsClient) AuthMethod() oidc.AuthMethod {
|
||||
return oidc.AuthMethodBasic
|
||||
}
|
||||
|
||||
// ResponseTypes returns nil as the types are only required for an authorization request
|
||||
func (c *clientCredentialsClient) ResponseTypes() []oidc.ResponseType {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GrantTypes returns the grant types supported by the machine users, which is currently only client credentials ([oidc.GrantTypeClientCredentials])
|
||||
func (c *clientCredentialsClient) GrantTypes() []oidc.GrantType {
|
||||
return []oidc.GrantType{
|
||||
oidc.GrantTypeClientCredentials,
|
||||
}
|
||||
}
|
||||
|
||||
// LoginURL returns an empty string as there is no login UI involved
|
||||
func (c *clientCredentialsClient) LoginURL(_ string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// IDTokenLifetime returns 0 as there is no id_token issued
|
||||
func (c *clientCredentialsClient) IDTokenLifetime() time.Duration {
|
||||
return 0
|
||||
}
|
||||
|
||||
// DevMode returns false as there is no dev mode
|
||||
func (c *clientCredentialsClient) DevMode() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// RestrictAdditionalIdTokenScopes returns nil as no id_token is issued
|
||||
func (c *clientCredentialsClient) RestrictAdditionalIdTokenScopes() func(scopes []string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RestrictAdditionalAccessTokenScopes returns the scope allowed for the token to be created because of the client credentials request
|
||||
// currently it allows all scopes to be used in the access token
|
||||
func (c *clientCredentialsClient) RestrictAdditionalAccessTokenScopes() func(scopes []string) []string {
|
||||
return func(scopes []string) []string {
|
||||
return scopes
|
||||
}
|
||||
}
|
||||
|
||||
// IsScopeAllowed returns null false as the check is executed during the auth request validation
|
||||
func (c *clientCredentialsClient) IsScopeAllowed(scope string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IDTokenUserinfoClaimsAssertion returns null false as no id_token is issued
|
||||
func (c *clientCredentialsClient) IDTokenUserinfoClaimsAssertion() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ClockSkew enable handling clock skew of the token validation. The duration (0-5s) will be added to exp claim and subtracted from iats,
|
||||
// auth_time and nbf of the token to be created because of the client credentials request.
|
||||
// It returns 0 as clock skew is not implemented on machine users.
|
||||
func (c *clientCredentialsClient) ClockSkew() time.Duration {
|
||||
return 0
|
||||
}
|
@ -3,6 +3,7 @@ package command
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
@ -17,6 +18,8 @@ type MachineWriteModel struct {
|
||||
Name string
|
||||
Description string
|
||||
UserState domain.UserState
|
||||
|
||||
ClientSecret *crypto.CryptoValue
|
||||
}
|
||||
|
||||
func NewMachineWriteModel(userID, resourceOwner string) *MachineWriteModel {
|
||||
@ -63,6 +66,10 @@ func (wm *MachineWriteModel) Reduce() error {
|
||||
}
|
||||
case *user.UserRemovedEvent:
|
||||
wm.UserState = domain.UserStateDeleted
|
||||
case *user.MachineSecretSetEvent:
|
||||
wm.ClientSecret = e.ClientSecret
|
||||
case *user.MachineSecretRemovedEvent:
|
||||
wm.ClientSecret = nil
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
@ -81,7 +88,9 @@ func (wm *MachineWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
user.UserUnlockedType,
|
||||
user.UserDeactivatedType,
|
||||
user.UserReactivatedType,
|
||||
user.UserRemovedType).
|
||||
user.UserRemovedType,
|
||||
user.MachineSecretSetType,
|
||||
user.MachineSecretRemovedType).
|
||||
Builder()
|
||||
}
|
||||
|
||||
|
171
internal/command/user_machine_secret.go
Normal file
171
internal/command/user_machine_secret.go
Normal file
@ -0,0 +1,171 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/command/preparation"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
caos_errs "github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
)
|
||||
|
||||
type GenerateMachineSecret struct {
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
}
|
||||
|
||||
func (c *Commands) GenerateMachineSecret(ctx context.Context, userID string, resourceOwner string, generator crypto.Generator, set *GenerateMachineSecret) (*domain.ObjectDetails, error) {
|
||||
agg := user.NewAggregate(userID, resourceOwner)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, prepareGenerateMachineSecret(agg, generator, set))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &domain.ObjectDetails{
|
||||
Sequence: events[len(events)-1].Sequence(),
|
||||
EventDate: events[len(events)-1].CreationDate(),
|
||||
ResourceOwner: events[len(events)-1].Aggregate().ResourceOwner,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func prepareGenerateMachineSecret(a *user.Aggregate, generator crypto.Generator, set *GenerateMachineSecret) preparation.Validation {
|
||||
return func() (_ preparation.CreateCommands, err error) {
|
||||
if a.ResourceOwner == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-x0992n", "Errors.ResourceOwnerMissing")
|
||||
}
|
||||
if a.ID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-bzoqjs", "Errors.User.UserIDMissing")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
writeModel, err := getMachineWriteModel(ctx, a.ID, a.ResourceOwner, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isUserStateExists(writeModel.UserState) {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-x8910n", "Errors.User.NotExisting")
|
||||
}
|
||||
set.ClientID = writeModel.UserName
|
||||
|
||||
clientSecret, secretString, err := domain.NewMachineClientSecret(generator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
set.ClientSecret = secretString
|
||||
|
||||
return []eventstore.Command{
|
||||
user.NewMachineSecretSetEvent(ctx, &a.Aggregate, clientSecret),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) RemoveMachineSecret(ctx context.Context, userID string, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||
agg := user.NewAggregate(userID, resourceOwner)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, prepareRemoveMachineSecret(agg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &domain.ObjectDetails{
|
||||
Sequence: events[len(events)-1].Sequence(),
|
||||
EventDate: events[len(events)-1].CreationDate(),
|
||||
ResourceOwner: events[len(events)-1].Aggregate().ResourceOwner,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func prepareRemoveMachineSecret(a *user.Aggregate) preparation.Validation {
|
||||
return func() (_ preparation.CreateCommands, err error) {
|
||||
if a.ResourceOwner == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-0qp2hus", "Errors.ResourceOwnerMissing")
|
||||
}
|
||||
if a.ID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-bzosjs", "Errors.User.UserIDMissing")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
writeModel, err := getMachineWriteModel(ctx, a.ID, a.ResourceOwner, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isUserStateExists(writeModel.UserState) {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-x7s802", "Errors.User.NotExisting")
|
||||
}
|
||||
if writeModel.ClientSecret == nil {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-coi82n", "Errors.User.Machine.Secret.NotExisting")
|
||||
}
|
||||
return []eventstore.Command{
|
||||
user.NewMachineSecretRemovedEvent(ctx, &a.Aggregate),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) VerifyMachineSecret(ctx context.Context, userID string, resourceOwner string, secret string) (*domain.ObjectDetails, error) {
|
||||
agg := user.NewAggregate(userID, resourceOwner)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, prepareVerifyMachineSecret(agg, secret, c.userPasswordAlg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events, err := c.eventstore.Push(ctx, cmds...)
|
||||
for _, cmd := range cmds {
|
||||
if cmd.Type() == user.MachineSecretCheckFailedType {
|
||||
logging.OnError(err).Error("could not push event MachineSecretCheckFailed")
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-3kjh", "Errors.User.Machine.Secret.Invalid")
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &domain.ObjectDetails{
|
||||
Sequence: events[len(events)-1].Sequence(),
|
||||
EventDate: events[len(events)-1].CreationDate(),
|
||||
ResourceOwner: events[len(events)-1].Aggregate().ResourceOwner,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func prepareVerifyMachineSecret(a *user.Aggregate, secret string, algorithm crypto.HashAlgorithm) preparation.Validation {
|
||||
return func() (_ preparation.CreateCommands, err error) {
|
||||
if a.ResourceOwner == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-0qp2hus", "Errors.ResourceOwnerMissing")
|
||||
}
|
||||
if a.ID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-bzosjs", "Errors.User.UserIDMissing")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
writeModel, err := getMachineWriteModel(ctx, a.ID, a.ResourceOwner, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isUserStateExists(writeModel.UserState) {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-569sh2o", "Errors.User.NotExisting")
|
||||
}
|
||||
if writeModel.ClientSecret == nil {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-x8910n", "Errors.User.Machine.Secret.NotExisting")
|
||||
}
|
||||
err = crypto.CompareHash(writeModel.ClientSecret, []byte(secret), algorithm)
|
||||
if err == nil {
|
||||
return []eventstore.Command{
|
||||
user.NewMachineSecretCheckSucceededEvent(ctx, &a.Aggregate),
|
||||
}, nil
|
||||
}
|
||||
return []eventstore.Command{
|
||||
user.NewMachineSecretCheckFailedEvent(ctx, &a.Aggregate),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
543
internal/command/user_machine_secret_test.go
Normal file
543
internal/command/user_machine_secret_test.go
Normal file
@ -0,0 +1,543 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
caos_errs "github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/repository"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
)
|
||||
|
||||
func TestCommandSide_GenerateMachineSecret(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
userID string
|
||||
resourceOwner string
|
||||
generator crypto.Generator
|
||||
set *GenerateMachineSecret
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
secret *GenerateMachineSecret
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "user invalid, invalid argument error userID",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "",
|
||||
resourceOwner: "org1",
|
||||
generator: GetMockSecretGenerator(t),
|
||||
set: nil,
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "user invalid, invalid argument error resourceowner",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
resourceOwner: "",
|
||||
generator: GetMockSecretGenerator(t),
|
||||
set: nil,
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "user not existing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
generator: GetMockSecretGenerator(t),
|
||||
set: nil,
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add machine secret, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewMachineAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"user1",
|
||||
"username",
|
||||
"user",
|
||||
false,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
user.NewMachineSecretSetEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("a"),
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
generator: GetMockSecretGenerator(t),
|
||||
set: &GenerateMachineSecret{},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
secret: &GenerateMachineSecret{
|
||||
ClientID: "user1",
|
||||
ClientSecret: "a",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
}
|
||||
got, err := r.GenerateMachineSecret(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.generator, tt.args.set)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
if tt.res.err != nil && !tt.res.err(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
assert.Equal(t, tt.args.set.ClientID, tt.res.secret.ClientID)
|
||||
assert.Equal(t, tt.args.set.ClientSecret, tt.res.secret.ClientSecret)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_RemoveMachineSecret(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
userID string
|
||||
resourceOwner string
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "user invalid, invalid argument error userID",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "user invalid, invalid argument error resourceowner",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
resourceOwner: "",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "user not existing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "user existing without secret, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewMachineAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"user1",
|
||||
"username",
|
||||
"user",
|
||||
false,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remove machine secret, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewMachineAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"user1",
|
||||
"username",
|
||||
"user",
|
||||
false,
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
user.NewMachineSecretSetEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("a"),
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
user.NewMachineSecretRemovedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
}
|
||||
got, err := r.RemoveMachineSecret(tt.args.ctx, tt.args.userID, tt.args.resourceOwner)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
if tt.res.err != nil && !tt.res.err(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_VerifyMachineSecret(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
userID string
|
||||
resourceOwner string
|
||||
secret string
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "user invalid, invalid argument error userID",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "user invalid, invalid argument error resourceowner",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
resourceOwner: "",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "user not existing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "user existing without secret, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewMachineAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"user1",
|
||||
"username",
|
||||
"user",
|
||||
false,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "verify machine secret, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewMachineAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"user1",
|
||||
"username",
|
||||
"user",
|
||||
false,
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
user.NewMachineSecretSetEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "bcrypt",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("$2a$14$HxC7TAXMeowdqHdSBUfsjOUc0IGajYeApxdYl9lAYC0duZmSkgFia"),
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
user.NewMachineSecretCheckSucceededEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
secret: "test",
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "verify machine secret, failed",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewMachineAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"user1",
|
||||
"username",
|
||||
"user",
|
||||
false,
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
user.NewMachineSecretSetEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "bcrypt",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("$2a$14$HxC7TAXMeowdqHdSBUfsjOUc0IGajYeApxdYl9lAYC0duZmSkgFia"),
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
user.NewMachineSecretCheckFailedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
secret: "wrong",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
userPasswordAlg: crypto.NewBCrypt(14),
|
||||
}
|
||||
got, err := r.VerifyMachineSecret(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.secret)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
if tt.res.err != nil && !tt.res.err(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
14
internal/domain/machine_secret.go
Normal file
14
internal/domain/machine_secret.go
Normal file
@ -0,0 +1,14 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func NewMachineClientSecret(generator crypto.Generator) (*crypto.CryptoValue, string, error) {
|
||||
cryptoValue, stringSecret, err := crypto.NewCode(generator)
|
||||
if err != nil {
|
||||
return nil, "", errors.ThrowInternal(err, "MODEL-57cjsiw", "Errors.User.Machine.Secret.CouldNotGenerate")
|
||||
}
|
||||
return cryptoValue, stringSecret, nil
|
||||
}
|
@ -20,18 +20,18 @@ var (
|
||||
", members.user_id" +
|
||||
", members.roles" +
|
||||
", projections.login_names2.login_name" +
|
||||
", projections.users6_humans.email" +
|
||||
", projections.users6_humans.first_name" +
|
||||
", projections.users6_humans.last_name" +
|
||||
", projections.users6_humans.display_name" +
|
||||
", projections.users6_machines.name" +
|
||||
", projections.users6_humans.avatar_key" +
|
||||
", projections.users7_humans.email" +
|
||||
", projections.users7_humans.first_name" +
|
||||
", projections.users7_humans.last_name" +
|
||||
", projections.users7_humans.display_name" +
|
||||
", projections.users7_machines.name" +
|
||||
", projections.users7_humans.avatar_key" +
|
||||
", COUNT(*) OVER () " +
|
||||
"FROM projections.instance_members3 AS members " +
|
||||
"LEFT JOIN projections.users6_humans " +
|
||||
"ON members.user_id = projections.users6_humans.user_id AND members.instance_id = projections.users6_humans.instance_id " +
|
||||
"LEFT JOIN projections.users6_machines " +
|
||||
"ON members.user_id = projections.users6_machines.user_id AND members.instance_id = projections.users6_machines.instance_id " +
|
||||
"LEFT JOIN projections.users7_humans " +
|
||||
"ON members.user_id = projections.users7_humans.user_id AND members.instance_id = projections.users7_humans.instance_id " +
|
||||
"LEFT JOIN projections.users7_machines " +
|
||||
"ON members.user_id = projections.users7_machines.user_id AND members.instance_id = projections.users7_machines.instance_id " +
|
||||
"LEFT JOIN projections.login_names2 " +
|
||||
"ON members.user_id = projections.login_names2.user_id AND members.instance_id = projections.login_names2.instance_id " +
|
||||
"WHERE projections.login_names2.is_primary = $1")
|
||||
|
@ -20,20 +20,20 @@ var (
|
||||
", members.user_id" +
|
||||
", members.roles" +
|
||||
", projections.login_names2.login_name" +
|
||||
", projections.users6_humans.email" +
|
||||
", projections.users6_humans.first_name" +
|
||||
", projections.users6_humans.last_name" +
|
||||
", projections.users6_humans.display_name" +
|
||||
", projections.users6_machines.name" +
|
||||
", projections.users6_humans.avatar_key" +
|
||||
", projections.users7_humans.email" +
|
||||
", projections.users7_humans.first_name" +
|
||||
", projections.users7_humans.last_name" +
|
||||
", projections.users7_humans.display_name" +
|
||||
", projections.users7_machines.name" +
|
||||
", projections.users7_humans.avatar_key" +
|
||||
", COUNT(*) OVER () " +
|
||||
"FROM projections.org_members3 AS members " +
|
||||
"LEFT JOIN projections.users6_humans " +
|
||||
"ON members.user_id = projections.users6_humans.user_id " +
|
||||
"AND members.instance_id = projections.users6_humans.instance_id " +
|
||||
"LEFT JOIN projections.users6_machines " +
|
||||
"ON members.user_id = projections.users6_machines.user_id " +
|
||||
"AND members.instance_id = projections.users6_machines.instance_id " +
|
||||
"LEFT JOIN projections.users7_humans " +
|
||||
"ON members.user_id = projections.users7_humans.user_id " +
|
||||
"AND members.instance_id = projections.users7_humans.instance_id " +
|
||||
"LEFT JOIN projections.users7_machines " +
|
||||
"ON members.user_id = projections.users7_machines.user_id " +
|
||||
"AND members.instance_id = projections.users7_machines.instance_id " +
|
||||
"LEFT JOIN projections.login_names2 " +
|
||||
"ON members.user_id = projections.login_names2.user_id " +
|
||||
"AND members.instance_id = projections.login_names2.instance_id " +
|
||||
|
@ -20,20 +20,20 @@ var (
|
||||
", members.user_id" +
|
||||
", members.roles" +
|
||||
", projections.login_names2.login_name" +
|
||||
", projections.users6_humans.email" +
|
||||
", projections.users6_humans.first_name" +
|
||||
", projections.users6_humans.last_name" +
|
||||
", projections.users6_humans.display_name" +
|
||||
", projections.users6_machines.name" +
|
||||
", projections.users6_humans.avatar_key" +
|
||||
", projections.users7_humans.email" +
|
||||
", projections.users7_humans.first_name" +
|
||||
", projections.users7_humans.last_name" +
|
||||
", projections.users7_humans.display_name" +
|
||||
", projections.users7_machines.name" +
|
||||
", projections.users7_humans.avatar_key" +
|
||||
", COUNT(*) OVER () " +
|
||||
"FROM projections.project_grant_members3 AS members " +
|
||||
"LEFT JOIN projections.users6_humans " +
|
||||
"ON members.user_id = projections.users6_humans.user_id " +
|
||||
"AND members.instance_id = projections.users6_humans.instance_id " +
|
||||
"LEFT JOIN projections.users6_machines " +
|
||||
"ON members.user_id = projections.users6_machines.user_id " +
|
||||
"AND members.instance_id = projections.users6_machines.instance_id " +
|
||||
"LEFT JOIN projections.users7_humans " +
|
||||
"ON members.user_id = projections.users7_humans.user_id " +
|
||||
"AND members.instance_id = projections.users7_humans.instance_id " +
|
||||
"LEFT JOIN projections.users7_machines " +
|
||||
"ON members.user_id = projections.users7_machines.user_id " +
|
||||
"AND members.instance_id = projections.users7_machines.instance_id " +
|
||||
"LEFT JOIN projections.login_names2 " +
|
||||
"ON members.user_id = projections.login_names2.user_id " +
|
||||
"AND members.instance_id = projections.login_names2.instance_id " +
|
||||
|
@ -20,20 +20,20 @@ var (
|
||||
", members.user_id" +
|
||||
", members.roles" +
|
||||
", projections.login_names2.login_name" +
|
||||
", projections.users6_humans.email" +
|
||||
", projections.users6_humans.first_name" +
|
||||
", projections.users6_humans.last_name" +
|
||||
", projections.users6_humans.display_name" +
|
||||
", projections.users6_machines.name" +
|
||||
", projections.users6_humans.avatar_key" +
|
||||
", projections.users7_humans.email" +
|
||||
", projections.users7_humans.first_name" +
|
||||
", projections.users7_humans.last_name" +
|
||||
", projections.users7_humans.display_name" +
|
||||
", projections.users7_machines.name" +
|
||||
", projections.users7_humans.avatar_key" +
|
||||
", COUNT(*) OVER () " +
|
||||
"FROM projections.project_members3 AS members " +
|
||||
"LEFT JOIN projections.users6_humans " +
|
||||
"ON members.user_id = projections.users6_humans.user_id " +
|
||||
"AND members.instance_id = projections.users6_humans.instance_id " +
|
||||
"LEFT JOIN projections.users6_machines " +
|
||||
"ON members.user_id = projections.users6_machines.user_id " +
|
||||
"AND members.instance_id = projections.users6_machines.instance_id " +
|
||||
"LEFT JOIN projections.users7_humans " +
|
||||
"ON members.user_id = projections.users7_humans.user_id " +
|
||||
"AND members.instance_id = projections.users7_humans.instance_id " +
|
||||
"LEFT JOIN projections.users7_machines " +
|
||||
"ON members.user_id = projections.users7_machines.user_id " +
|
||||
"AND members.instance_id = projections.users7_machines.instance_id " +
|
||||
"LEFT JOIN projections.login_names2 " +
|
||||
"ON members.user_id = projections.login_names2.user_id " +
|
||||
"AND members.instance_id = projections.login_names2.instance_id " +
|
||||
|
@ -19,7 +19,7 @@ type userProjection struct {
|
||||
}
|
||||
|
||||
const (
|
||||
UserTable = "projections.users6"
|
||||
UserTable = "projections.users7"
|
||||
UserHumanTable = UserTable + "_" + UserHumanSuffix
|
||||
UserMachineTable = UserTable + "_" + UserMachineSuffix
|
||||
UserNotifyTable = UserTable + "_" + UserNotifySuffix
|
||||
@ -62,6 +62,7 @@ const (
|
||||
MachineUserInstanceIDCol = "instance_id"
|
||||
MachineNameCol = "name"
|
||||
MachineDescriptionCol = "description"
|
||||
MachineHasSecretCol = "has_secret"
|
||||
|
||||
// notify
|
||||
UserNotifySuffix = "notifications"
|
||||
@ -120,6 +121,7 @@ func newUserProjection(ctx context.Context, config crdb.StatementHandlerConfig)
|
||||
crdb.NewColumn(MachineUserInstanceIDCol, crdb.ColumnTypeText),
|
||||
crdb.NewColumn(MachineNameCol, crdb.ColumnTypeText),
|
||||
crdb.NewColumn(MachineDescriptionCol, crdb.ColumnTypeText, crdb.Nullable()),
|
||||
crdb.NewColumn(MachineHasSecretCol, crdb.ColumnTypeBool, crdb.Default(false)),
|
||||
},
|
||||
crdb.NewPrimaryKey(MachineUserInstanceIDCol, MachineUserIDCol),
|
||||
UserMachineSuffix,
|
||||
@ -276,6 +278,14 @@ func (p *userProjection) reducers() []handler.AggregateReducer {
|
||||
Event: user.HumanPasswordChangedType,
|
||||
Reduce: p.reduceHumanPasswordChanged,
|
||||
},
|
||||
{
|
||||
Event: user.MachineSecretSetType,
|
||||
Reduce: p.reduceMachineSecretSet,
|
||||
},
|
||||
{
|
||||
Event: user.MachineSecretRemovedType,
|
||||
Reduce: p.reduceMachineSecretRemoved,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -907,6 +917,67 @@ func (p *userProjection) reduceHumanPasswordChanged(event eventstore.Event) (*ha
|
||||
), nil
|
||||
}
|
||||
|
||||
func (p *userProjection) reduceMachineSecretSet(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, ok := event.(*user.MachineSecretSetEvent)
|
||||
if !ok {
|
||||
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-x0p1n1i", "reduce.wrong.event.type %s", user.MachineSecretSetType)
|
||||
}
|
||||
return crdb.NewMultiStatement(
|
||||
e,
|
||||
crdb.AddUpdateStatement(
|
||||
[]handler.Column{
|
||||
handler.NewCol(UserChangeDateCol, e.CreationDate()),
|
||||
handler.NewCol(UserSequenceCol, e.Sequence()),
|
||||
},
|
||||
[]handler.Condition{
|
||||
handler.NewCond(UserIDCol, e.Aggregate().ID),
|
||||
handler.NewCond(UserInstanceIDCol, e.Aggregate().InstanceID),
|
||||
},
|
||||
),
|
||||
crdb.AddUpdateStatement(
|
||||
[]handler.Column{
|
||||
handler.NewCol(MachineHasSecretCol, true),
|
||||
},
|
||||
[]handler.Condition{
|
||||
handler.NewCond(MachineUserIDCol, e.Aggregate().ID),
|
||||
handler.NewCond(MachineUserInstanceIDCol, e.Aggregate().InstanceID),
|
||||
},
|
||||
crdb.WithTableSuffix(UserMachineSuffix),
|
||||
),
|
||||
), nil
|
||||
}
|
||||
|
||||
func (p *userProjection) reduceMachineSecretRemoved(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, ok := event.(*user.MachineSecretRemovedEvent)
|
||||
if !ok {
|
||||
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-x0p6n1i", "reduce.wrong.event.type %s", user.MachineSecretRemovedType)
|
||||
}
|
||||
|
||||
return crdb.NewMultiStatement(
|
||||
e,
|
||||
crdb.AddUpdateStatement(
|
||||
[]handler.Column{
|
||||
handler.NewCol(UserChangeDateCol, e.CreationDate()),
|
||||
handler.NewCol(UserSequenceCol, e.Sequence()),
|
||||
},
|
||||
[]handler.Condition{
|
||||
handler.NewCond(UserIDCol, e.Aggregate().ID),
|
||||
handler.NewCond(UserInstanceIDCol, e.Aggregate().InstanceID),
|
||||
},
|
||||
),
|
||||
crdb.AddUpdateStatement(
|
||||
[]handler.Column{
|
||||
handler.NewCol(MachineHasSecretCol, false),
|
||||
},
|
||||
[]handler.Condition{
|
||||
handler.NewCond(MachineUserIDCol, e.Aggregate().ID),
|
||||
handler.NewCond(MachineUserInstanceIDCol, e.Aggregate().InstanceID),
|
||||
},
|
||||
crdb.WithTableSuffix(UserMachineSuffix),
|
||||
),
|
||||
), nil
|
||||
}
|
||||
|
||||
func (p *userProjection) reduceMachineAdded(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, ok := event.(*user.MachineAddedEvent)
|
||||
if !ok {
|
||||
|
@ -51,7 +51,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.users6 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedStmt: "INSERT INTO projections.users7 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
anyArg{},
|
||||
@ -65,7 +65,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.users6_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
|
||||
expectedStmt: "INSERT INTO projections.users7_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
@ -80,7 +80,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.users6_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)",
|
||||
expectedStmt: "INSERT INTO projections.users7_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
@ -120,7 +120,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.users6 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedStmt: "INSERT INTO projections.users7 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
anyArg{},
|
||||
@ -134,7 +134,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.users6_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
|
||||
expectedStmt: "INSERT INTO projections.users7_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
@ -149,7 +149,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.users6_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)",
|
||||
expectedStmt: "INSERT INTO projections.users7_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
@ -184,7 +184,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.users6 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedStmt: "INSERT INTO projections.users7 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
anyArg{},
|
||||
@ -198,7 +198,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.users6_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
|
||||
expectedStmt: "INSERT INTO projections.users7_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
@ -213,7 +213,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.users6_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)",
|
||||
expectedStmt: "INSERT INTO projections.users7_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
@ -253,7 +253,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.users6 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedStmt: "INSERT INTO projections.users7 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
anyArg{},
|
||||
@ -267,7 +267,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.users6_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
|
||||
expectedStmt: "INSERT INTO projections.users7_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
@ -282,7 +282,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.users6_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)",
|
||||
expectedStmt: "INSERT INTO projections.users7_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
@ -322,7 +322,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.users6 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedStmt: "INSERT INTO projections.users7 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
anyArg{},
|
||||
@ -336,7 +336,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.users6_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
|
||||
expectedStmt: "INSERT INTO projections.users7_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
@ -351,7 +351,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.users6_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)",
|
||||
expectedStmt: "INSERT INTO projections.users7_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
@ -386,7 +386,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.users6 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedStmt: "INSERT INTO projections.users7 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
anyArg{},
|
||||
@ -400,7 +400,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.users6_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
|
||||
expectedStmt: "INSERT INTO projections.users7_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
@ -415,7 +415,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.users6_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)",
|
||||
expectedStmt: "INSERT INTO projections.users7_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
@ -445,7 +445,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6 SET state = $1 WHERE (id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users7 SET state = $1 WHERE (id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
domain.UserStateInitial,
|
||||
"agg-id",
|
||||
@ -473,7 +473,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6 SET state = $1 WHERE (id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users7 SET state = $1 WHERE (id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
domain.UserStateInitial,
|
||||
"agg-id",
|
||||
@ -501,7 +501,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6 SET state = $1 WHERE (id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users7 SET state = $1 WHERE (id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
domain.UserStateActive,
|
||||
"agg-id",
|
||||
@ -529,7 +529,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6 SET state = $1 WHERE (id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users7 SET state = $1 WHERE (id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
domain.UserStateActive,
|
||||
"agg-id",
|
||||
@ -557,7 +557,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedStmt: "UPDATE projections.users7 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
domain.UserStateLocked,
|
||||
@ -587,7 +587,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedStmt: "UPDATE projections.users7 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
domain.UserStateActive,
|
||||
@ -617,7 +617,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedStmt: "UPDATE projections.users7 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
domain.UserStateInactive,
|
||||
@ -647,7 +647,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedStmt: "UPDATE projections.users7 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
domain.UserStateActive,
|
||||
@ -677,7 +677,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "DELETE FROM projections.users6 WHERE (id = $1) AND (instance_id = $2)",
|
||||
expectedStmt: "DELETE FROM projections.users7 WHERE (id = $1) AND (instance_id = $2)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
@ -706,7 +706,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6 SET (change_date, username, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedStmt: "UPDATE projections.users7 SET (change_date, username, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
"username",
|
||||
@ -738,7 +738,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6 SET (change_date, username, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedStmt: "UPDATE projections.users7 SET (change_date, username, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
"id@temporary.domain",
|
||||
@ -775,7 +775,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -784,7 +784,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6_humans SET (first_name, last_name, nick_name, display_name, preferred_language, gender) = ($1, $2, $3, $4, $5, $6) WHERE (user_id = $7) AND (instance_id = $8)",
|
||||
expectedStmt: "UPDATE projections.users7_humans SET (first_name, last_name, nick_name, display_name, preferred_language, gender) = ($1, $2, $3, $4, $5, $6) WHERE (user_id = $7) AND (instance_id = $8)",
|
||||
expectedArgs: []interface{}{
|
||||
"first-name",
|
||||
"last-name",
|
||||
@ -824,7 +824,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -833,7 +833,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6_humans SET (first_name, last_name, nick_name, display_name, preferred_language, gender) = ($1, $2, $3, $4, $5, $6) WHERE (user_id = $7) AND (instance_id = $8)",
|
||||
expectedStmt: "UPDATE projections.users7_humans SET (first_name, last_name, nick_name, display_name, preferred_language, gender) = ($1, $2, $3, $4, $5, $6) WHERE (user_id = $7) AND (instance_id = $8)",
|
||||
expectedArgs: []interface{}{
|
||||
"first-name",
|
||||
"last-name",
|
||||
@ -868,7 +868,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -877,7 +877,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users7_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
"+41 00 000 00 00",
|
||||
false,
|
||||
@ -886,7 +886,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6_notifications SET last_phone = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users7_notifications SET last_phone = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
&sql.NullString{String: "+41 00 000 00 00", Valid: true},
|
||||
"agg-id",
|
||||
@ -916,7 +916,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -925,7 +925,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users7_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
"+41 00 000 00 00",
|
||||
false,
|
||||
@ -934,7 +934,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6_notifications SET last_phone = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users7_notifications SET last_phone = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
&sql.NullString{String: "+41 00 000 00 00", Valid: true},
|
||||
"agg-id",
|
||||
@ -962,7 +962,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -971,7 +971,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users7_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
nil,
|
||||
nil,
|
||||
@ -980,7 +980,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6_notifications SET (last_phone, verified_phone) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users7_notifications SET (last_phone, verified_phone) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
nil,
|
||||
nil,
|
||||
@ -1009,7 +1009,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -1018,7 +1018,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users7_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
nil,
|
||||
nil,
|
||||
@ -1027,7 +1027,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6_notifications SET (last_phone, verified_phone) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users7_notifications SET (last_phone, verified_phone) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
nil,
|
||||
nil,
|
||||
@ -1056,7 +1056,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -1065,7 +1065,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6_humans SET is_phone_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users7_humans SET is_phone_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
true,
|
||||
"agg-id",
|
||||
@ -1073,7 +1073,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6_notifications SET verified_phone = last_phone WHERE (user_id = $1) AND (instance_id = $2)",
|
||||
expectedStmt: "UPDATE projections.users7_notifications SET verified_phone = last_phone WHERE (user_id = $1) AND (instance_id = $2)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
@ -1100,7 +1100,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -1109,7 +1109,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6_humans SET is_phone_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users7_humans SET is_phone_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
true,
|
||||
"agg-id",
|
||||
@ -1117,7 +1117,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6_notifications SET verified_phone = last_phone WHERE (user_id = $1) AND (instance_id = $2)",
|
||||
expectedStmt: "UPDATE projections.users7_notifications SET verified_phone = last_phone WHERE (user_id = $1) AND (instance_id = $2)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
@ -1146,7 +1146,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -1155,7 +1155,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6_humans SET (email, is_email_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users7_humans SET (email, is_email_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
"email@zitadel.com",
|
||||
false,
|
||||
@ -1164,7 +1164,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6_notifications SET last_email = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users7_notifications SET last_email = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
&sql.NullString{String: "email@zitadel.com", Valid: true},
|
||||
"agg-id",
|
||||
@ -1194,7 +1194,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -1203,7 +1203,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6_humans SET (email, is_email_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users7_humans SET (email, is_email_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
"email@zitadel.com",
|
||||
false,
|
||||
@ -1212,7 +1212,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6_notifications SET last_email = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users7_notifications SET last_email = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
&sql.NullString{String: "email@zitadel.com", Valid: true},
|
||||
"agg-id",
|
||||
@ -1240,7 +1240,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -1249,7 +1249,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6_humans SET is_email_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users7_humans SET is_email_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
true,
|
||||
"agg-id",
|
||||
@ -1257,7 +1257,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6_notifications SET verified_email = last_email WHERE (user_id = $1) AND (instance_id = $2)",
|
||||
expectedStmt: "UPDATE projections.users7_notifications SET verified_email = last_email WHERE (user_id = $1) AND (instance_id = $2)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
@ -1284,7 +1284,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -1293,7 +1293,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6_humans SET is_email_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users7_humans SET is_email_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
true,
|
||||
"agg-id",
|
||||
@ -1301,7 +1301,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6_notifications SET verified_email = last_email WHERE (user_id = $1) AND (instance_id = $2)",
|
||||
expectedStmt: "UPDATE projections.users7_notifications SET verified_email = last_email WHERE (user_id = $1) AND (instance_id = $2)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
@ -1330,7 +1330,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -1339,7 +1339,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6_humans SET avatar_key = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users7_humans SET avatar_key = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
"users/agg-id/avatar",
|
||||
"agg-id",
|
||||
@ -1367,7 +1367,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -1376,7 +1376,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6_humans SET avatar_key = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users7_humans SET avatar_key = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
nil,
|
||||
"agg-id",
|
||||
@ -1407,7 +1407,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.users6 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedStmt: "INSERT INTO projections.users7 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
anyArg{},
|
||||
@ -1421,7 +1421,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.users6_machines (user_id, instance_id, name, description) VALUES ($1, $2, $3, $4)",
|
||||
expectedStmt: "INSERT INTO projections.users7_machines (user_id, instance_id, name, description) VALUES ($1, $2, $3, $4)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
@ -1454,7 +1454,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.users6 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedStmt: "INSERT INTO projections.users7 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
anyArg{},
|
||||
@ -1468,7 +1468,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.users6_machines (user_id, instance_id, name, description) VALUES ($1, $2, $3, $4)",
|
||||
expectedStmt: "INSERT INTO projections.users7_machines (user_id, instance_id, name, description) VALUES ($1, $2, $3, $4)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
@ -1500,7 +1500,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -1509,7 +1509,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6_machines SET (name, description) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users7_machines SET (name, description) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
"machine-name",
|
||||
"description",
|
||||
@ -1540,7 +1540,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -1549,7 +1549,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6_machines SET name = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users7_machines SET name = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
"machine-name",
|
||||
"agg-id",
|
||||
@ -1579,7 +1579,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -1588,7 +1588,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6_machines SET description = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users7_machines SET description = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
"description",
|
||||
"agg-id",
|
||||
@ -1618,6 +1618,82 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "reduceMachineSecretSet",
|
||||
args: args{
|
||||
event: getEvent(testEvent(
|
||||
repository.EventType(user.MachineSecretSetType),
|
||||
user.AggregateType,
|
||||
[]byte(`{
|
||||
"client_secret": {}
|
||||
}`),
|
||||
), user.MachineSecretSetEventMapper),
|
||||
},
|
||||
reduce: (&userProjection{}).reduceMachineSecretSet,
|
||||
want: wantReduce{
|
||||
aggregateType: user.AggregateType,
|
||||
sequence: 15,
|
||||
previousSequence: 10,
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users7_machines SET has_secret = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
true,
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "reduceMachineSecretSet",
|
||||
args: args{
|
||||
event: getEvent(testEvent(
|
||||
repository.EventType(user.MachineSecretRemovedType),
|
||||
user.AggregateType,
|
||||
[]byte(`{}`),
|
||||
), user.MachineSecretRemovedEventMapper),
|
||||
},
|
||||
reduce: (&userProjection{}).reduceMachineSecretRemoved,
|
||||
want: wantReduce{
|
||||
aggregateType: user.AggregateType,
|
||||
sequence: 15,
|
||||
previousSequence: 10,
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users7_machines SET has_secret = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
false,
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "org reduceOwnerRemoved",
|
||||
reduce: (&userProjection{}).reduceOwnerRemoved,
|
||||
@ -1635,7 +1711,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users6 SET (change_date, sequence, owner_removed) = ($1, $2, $3) WHERE (instance_id = $4) AND (resource_owner = $5)",
|
||||
expectedStmt: "UPDATE projections.users7 SET (change_date, sequence, owner_removed) = ($1, $2, $3) WHERE (instance_id = $4) AND (resource_owner = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -1665,7 +1741,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "DELETE FROM projections.users6 WHERE (instance_id = $1)",
|
||||
expectedStmt: "DELETE FROM projections.users7 WHERE (instance_id = $1)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
},
|
||||
|
@ -90,6 +90,7 @@ type Phone struct {
|
||||
type Machine struct {
|
||||
Name string
|
||||
Description string
|
||||
HasSecret bool
|
||||
}
|
||||
|
||||
type NotifyUser struct {
|
||||
@ -277,6 +278,10 @@ var (
|
||||
name: projection.MachineDescriptionCol,
|
||||
table: machineTable,
|
||||
}
|
||||
MachineHasSecretCol = Column{
|
||||
name: projection.MachineHasSecretCol,
|
||||
table: machineTable,
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
@ -747,6 +752,7 @@ func prepareUserQuery() (sq.SelectBuilder, func(*sql.Row) (*User, error)) {
|
||||
MachineUserIDCol.identifier(),
|
||||
MachineNameCol.identifier(),
|
||||
MachineDescriptionCol.identifier(),
|
||||
MachineHasSecretCol.identifier(),
|
||||
countColumn.identifier(),
|
||||
).
|
||||
From(userTable.identifier()).
|
||||
@ -782,6 +788,7 @@ func prepareUserQuery() (sq.SelectBuilder, func(*sql.Row) (*User, error)) {
|
||||
machineID := sql.NullString{}
|
||||
name := sql.NullString{}
|
||||
description := sql.NullString{}
|
||||
hasSecret := sql.NullBool{}
|
||||
|
||||
err := row.Scan(
|
||||
&u.ID,
|
||||
@ -809,6 +816,7 @@ func prepareUserQuery() (sq.SelectBuilder, func(*sql.Row) (*User, error)) {
|
||||
&machineID,
|
||||
&name,
|
||||
&description,
|
||||
&hasSecret,
|
||||
&count,
|
||||
)
|
||||
|
||||
@ -839,6 +847,7 @@ func prepareUserQuery() (sq.SelectBuilder, func(*sql.Row) (*User, error)) {
|
||||
u.Machine = &Machine{
|
||||
Name: name.String,
|
||||
Description: description.String,
|
||||
HasSecret: hasSecret.Bool,
|
||||
}
|
||||
}
|
||||
return u, nil
|
||||
@ -1209,6 +1218,7 @@ func prepareUsersQuery() (sq.SelectBuilder, func(*sql.Rows) (*Users, error)) {
|
||||
MachineUserIDCol.identifier(),
|
||||
MachineNameCol.identifier(),
|
||||
MachineDescriptionCol.identifier(),
|
||||
MachineHasSecretCol.identifier(),
|
||||
countColumn.identifier()).
|
||||
From(userTable.identifier()).
|
||||
LeftJoin(join(HumanUserIDCol, UserIDCol)).
|
||||
@ -1246,6 +1256,7 @@ func prepareUsersQuery() (sq.SelectBuilder, func(*sql.Rows) (*Users, error)) {
|
||||
machineID := sql.NullString{}
|
||||
name := sql.NullString{}
|
||||
description := sql.NullString{}
|
||||
hasSecret := sql.NullBool{}
|
||||
|
||||
err := rows.Scan(
|
||||
&u.ID,
|
||||
@ -1273,6 +1284,7 @@ func prepareUsersQuery() (sq.SelectBuilder, func(*sql.Rows) (*Users, error)) {
|
||||
&machineID,
|
||||
&name,
|
||||
&description,
|
||||
&hasSecret,
|
||||
&count,
|
||||
)
|
||||
if err != nil {
|
||||
@ -1302,6 +1314,7 @@ func prepareUsersQuery() (sq.SelectBuilder, func(*sql.Rows) (*Users, error)) {
|
||||
u.Machine = &Machine{
|
||||
Name: name.String,
|
||||
Description: description.String,
|
||||
HasSecret: hasSecret.Bool,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,14 +23,14 @@ var (
|
||||
", projections.user_grants3.roles" +
|
||||
", projections.user_grants3.state" +
|
||||
", projections.user_grants3.user_id" +
|
||||
", projections.users6.username" +
|
||||
", projections.users6.type" +
|
||||
", projections.users6.resource_owner" +
|
||||
", projections.users6_humans.first_name" +
|
||||
", projections.users6_humans.last_name" +
|
||||
", projections.users6_humans.email" +
|
||||
", projections.users6_humans.display_name" +
|
||||
", projections.users6_humans.avatar_key" +
|
||||
", projections.users7.username" +
|
||||
", projections.users7.type" +
|
||||
", projections.users7.resource_owner" +
|
||||
", projections.users7_humans.first_name" +
|
||||
", projections.users7_humans.last_name" +
|
||||
", projections.users7_humans.email" +
|
||||
", projections.users7_humans.display_name" +
|
||||
", projections.users7_humans.avatar_key" +
|
||||
", projections.login_names2.login_name" +
|
||||
", projections.user_grants3.resource_owner" +
|
||||
", projections.orgs.name" +
|
||||
@ -38,8 +38,8 @@ var (
|
||||
", projections.user_grants3.project_id" +
|
||||
", projections.projects3.name" +
|
||||
" FROM projections.user_grants3" +
|
||||
" LEFT JOIN projections.users6 ON projections.user_grants3.user_id = projections.users6.id AND projections.user_grants3.instance_id = projections.users6.instance_id" +
|
||||
" LEFT JOIN projections.users6_humans ON projections.user_grants3.user_id = projections.users6_humans.user_id AND projections.user_grants3.instance_id = projections.users6_humans.instance_id" +
|
||||
" LEFT JOIN projections.users7 ON projections.user_grants3.user_id = projections.users7.id AND projections.user_grants3.instance_id = projections.users7.instance_id" +
|
||||
" LEFT JOIN projections.users7_humans ON projections.user_grants3.user_id = projections.users7_humans.user_id AND projections.user_grants3.instance_id = projections.users7_humans.instance_id" +
|
||||
" LEFT JOIN projections.orgs ON projections.user_grants3.resource_owner = projections.orgs.id AND projections.user_grants3.instance_id = projections.orgs.instance_id" +
|
||||
" LEFT JOIN projections.projects3 ON projections.user_grants3.project_id = projections.projects3.id AND projections.user_grants3.instance_id = projections.projects3.instance_id" +
|
||||
" LEFT JOIN projections.login_names2 ON projections.user_grants3.user_id = projections.login_names2.user_id AND projections.user_grants3.instance_id = projections.login_names2.instance_id" +
|
||||
@ -77,14 +77,14 @@ var (
|
||||
", projections.user_grants3.roles" +
|
||||
", projections.user_grants3.state" +
|
||||
", projections.user_grants3.user_id" +
|
||||
", projections.users6.username" +
|
||||
", projections.users6.type" +
|
||||
", projections.users6.resource_owner" +
|
||||
", projections.users6_humans.first_name" +
|
||||
", projections.users6_humans.last_name" +
|
||||
", projections.users6_humans.email" +
|
||||
", projections.users6_humans.display_name" +
|
||||
", projections.users6_humans.avatar_key" +
|
||||
", projections.users7.username" +
|
||||
", projections.users7.type" +
|
||||
", projections.users7.resource_owner" +
|
||||
", projections.users7_humans.first_name" +
|
||||
", projections.users7_humans.last_name" +
|
||||
", projections.users7_humans.email" +
|
||||
", projections.users7_humans.display_name" +
|
||||
", projections.users7_humans.avatar_key" +
|
||||
", projections.login_names2.login_name" +
|
||||
", projections.user_grants3.resource_owner" +
|
||||
", projections.orgs.name" +
|
||||
@ -93,8 +93,8 @@ var (
|
||||
", projections.projects3.name" +
|
||||
", COUNT(*) OVER ()" +
|
||||
" FROM projections.user_grants3" +
|
||||
" LEFT JOIN projections.users6 ON projections.user_grants3.user_id = projections.users6.id AND projections.user_grants3.instance_id = projections.users6.instance_id" +
|
||||
" LEFT JOIN projections.users6_humans ON projections.user_grants3.user_id = projections.users6_humans.user_id AND projections.user_grants3.instance_id = projections.users6_humans.instance_id" +
|
||||
" LEFT JOIN projections.users7 ON projections.user_grants3.user_id = projections.users7.id AND projections.user_grants3.instance_id = projections.users7.instance_id" +
|
||||
" LEFT JOIN projections.users7_humans ON projections.user_grants3.user_id = projections.users7_humans.user_id AND projections.user_grants3.instance_id = projections.users7_humans.instance_id" +
|
||||
" LEFT JOIN projections.orgs ON projections.user_grants3.resource_owner = projections.orgs.id AND projections.user_grants3.instance_id = projections.orgs.instance_id" +
|
||||
" LEFT JOIN projections.projects3 ON projections.user_grants3.project_id = projections.projects3.id AND projections.user_grants3.instance_id = projections.projects3.instance_id" +
|
||||
" LEFT JOIN projections.login_names2 ON projections.user_grants3.user_id = projections.login_names2.user_id AND projections.user_grants3.instance_id = projections.login_names2.instance_id" +
|
||||
|
@ -23,41 +23,42 @@ var (
|
||||
preferredLoginNameQuery = `SELECT preferred_login_name.user_id, preferred_login_name.login_name, preferred_login_name.instance_id, preferred_login_name.user_owner_removed, preferred_login_name.policy_owner_removed, preferred_login_name.domain_owner_removed` +
|
||||
` FROM projections.login_names2 AS preferred_login_name` +
|
||||
` WHERE preferred_login_name.is_primary = $1`
|
||||
userQuery = `SELECT projections.users6.id,` +
|
||||
` projections.users6.creation_date,` +
|
||||
` projections.users6.change_date,` +
|
||||
` projections.users6.resource_owner,` +
|
||||
` projections.users6.sequence,` +
|
||||
` projections.users6.state,` +
|
||||
` projections.users6.type,` +
|
||||
` projections.users6.username,` +
|
||||
userQuery = `SELECT projections.users7.id,` +
|
||||
` projections.users7.creation_date,` +
|
||||
` projections.users7.change_date,` +
|
||||
` projections.users7.resource_owner,` +
|
||||
` projections.users7.sequence,` +
|
||||
` projections.users7.state,` +
|
||||
` projections.users7.type,` +
|
||||
` projections.users7.username,` +
|
||||
` login_names.loginnames,` +
|
||||
` preferred_login_name.login_name,` +
|
||||
` projections.users6_humans.user_id,` +
|
||||
` projections.users6_humans.first_name,` +
|
||||
` projections.users6_humans.last_name,` +
|
||||
` projections.users6_humans.nick_name,` +
|
||||
` projections.users6_humans.display_name,` +
|
||||
` projections.users6_humans.preferred_language,` +
|
||||
` projections.users6_humans.gender,` +
|
||||
` projections.users6_humans.avatar_key,` +
|
||||
` projections.users6_humans.email,` +
|
||||
` projections.users6_humans.is_email_verified,` +
|
||||
` projections.users6_humans.phone,` +
|
||||
` projections.users6_humans.is_phone_verified,` +
|
||||
` projections.users6_machines.user_id,` +
|
||||
` projections.users6_machines.name,` +
|
||||
` projections.users6_machines.description,` +
|
||||
` projections.users7_humans.user_id,` +
|
||||
` projections.users7_humans.first_name,` +
|
||||
` projections.users7_humans.last_name,` +
|
||||
` projections.users7_humans.nick_name,` +
|
||||
` projections.users7_humans.display_name,` +
|
||||
` projections.users7_humans.preferred_language,` +
|
||||
` projections.users7_humans.gender,` +
|
||||
` projections.users7_humans.avatar_key,` +
|
||||
` projections.users7_humans.email,` +
|
||||
` projections.users7_humans.is_email_verified,` +
|
||||
` projections.users7_humans.phone,` +
|
||||
` projections.users7_humans.is_phone_verified,` +
|
||||
` projections.users7_machines.user_id,` +
|
||||
` projections.users7_machines.name,` +
|
||||
` projections.users7_machines.description,` +
|
||||
` projections.users7_machines.has_secret,` +
|
||||
` COUNT(*) OVER ()` +
|
||||
` FROM projections.users6` +
|
||||
` LEFT JOIN projections.users6_humans ON projections.users6.id = projections.users6_humans.user_id AND projections.users6.instance_id = projections.users6_humans.instance_id` +
|
||||
` LEFT JOIN projections.users6_machines ON projections.users6.id = projections.users6_machines.user_id AND projections.users6.instance_id = projections.users6_machines.instance_id` +
|
||||
` FROM projections.users7` +
|
||||
` LEFT JOIN projections.users7_humans ON projections.users7.id = projections.users7_humans.user_id AND projections.users7.instance_id = projections.users7_humans.instance_id` +
|
||||
` LEFT JOIN projections.users7_machines ON projections.users7.id = projections.users7_machines.user_id AND projections.users7.instance_id = projections.users7_machines.instance_id` +
|
||||
` LEFT JOIN` +
|
||||
` (` + loginNamesQuery + `) AS login_names` +
|
||||
` ON login_names.user_id = projections.users6.id AND login_names.instance_id = projections.users6.instance_id` +
|
||||
` ON login_names.user_id = projections.users7.id AND login_names.instance_id = projections.users7.instance_id` +
|
||||
` LEFT JOIN` +
|
||||
` (` + preferredLoginNameQuery + `) AS preferred_login_name` +
|
||||
` ON preferred_login_name.user_id = projections.users6.id AND preferred_login_name.instance_id = projections.users6.instance_id`
|
||||
` ON preferred_login_name.user_id = projections.users7.id AND preferred_login_name.instance_id = projections.users7.instance_id`
|
||||
userCols = []string{
|
||||
"id",
|
||||
"creation_date",
|
||||
@ -86,23 +87,24 @@ var (
|
||||
"user_id",
|
||||
"name",
|
||||
"description",
|
||||
"has_secret",
|
||||
"count",
|
||||
}
|
||||
profileQuery = `SELECT projections.users6.id,` +
|
||||
` projections.users6.creation_date,` +
|
||||
` projections.users6.change_date,` +
|
||||
` projections.users6.resource_owner,` +
|
||||
` projections.users6.sequence,` +
|
||||
` projections.users6_humans.user_id,` +
|
||||
` projections.users6_humans.first_name,` +
|
||||
` projections.users6_humans.last_name,` +
|
||||
` projections.users6_humans.nick_name,` +
|
||||
` projections.users6_humans.display_name,` +
|
||||
` projections.users6_humans.preferred_language,` +
|
||||
` projections.users6_humans.gender,` +
|
||||
` projections.users6_humans.avatar_key` +
|
||||
` FROM projections.users6` +
|
||||
` LEFT JOIN projections.users6_humans ON projections.users6.id = projections.users6_humans.user_id AND projections.users6.instance_id = projections.users6_humans.instance_id`
|
||||
profileQuery = `SELECT projections.users7.id,` +
|
||||
` projections.users7.creation_date,` +
|
||||
` projections.users7.change_date,` +
|
||||
` projections.users7.resource_owner,` +
|
||||
` projections.users7.sequence,` +
|
||||
` projections.users7_humans.user_id,` +
|
||||
` projections.users7_humans.first_name,` +
|
||||
` projections.users7_humans.last_name,` +
|
||||
` projections.users7_humans.nick_name,` +
|
||||
` projections.users7_humans.display_name,` +
|
||||
` projections.users7_humans.preferred_language,` +
|
||||
` projections.users7_humans.gender,` +
|
||||
` projections.users7_humans.avatar_key` +
|
||||
` FROM projections.users7` +
|
||||
` LEFT JOIN projections.users7_humans ON projections.users7.id = projections.users7_humans.user_id AND projections.users7.instance_id = projections.users7_humans.instance_id`
|
||||
profileCols = []string{
|
||||
"id",
|
||||
"creation_date",
|
||||
@ -118,16 +120,16 @@ var (
|
||||
"gender",
|
||||
"avatar_key",
|
||||
}
|
||||
emailQuery = `SELECT projections.users6.id,` +
|
||||
` projections.users6.creation_date,` +
|
||||
` projections.users6.change_date,` +
|
||||
` projections.users6.resource_owner,` +
|
||||
` projections.users6.sequence,` +
|
||||
` projections.users6_humans.user_id,` +
|
||||
` projections.users6_humans.email,` +
|
||||
` projections.users6_humans.is_email_verified` +
|
||||
` FROM projections.users6` +
|
||||
` LEFT JOIN projections.users6_humans ON projections.users6.id = projections.users6_humans.user_id AND projections.users6.instance_id = projections.users6_humans.instance_id`
|
||||
emailQuery = `SELECT projections.users7.id,` +
|
||||
` projections.users7.creation_date,` +
|
||||
` projections.users7.change_date,` +
|
||||
` projections.users7.resource_owner,` +
|
||||
` projections.users7.sequence,` +
|
||||
` projections.users7_humans.user_id,` +
|
||||
` projections.users7_humans.email,` +
|
||||
` projections.users7_humans.is_email_verified` +
|
||||
` FROM projections.users7` +
|
||||
` LEFT JOIN projections.users7_humans ON projections.users7.id = projections.users7_humans.user_id AND projections.users7.instance_id = projections.users7_humans.instance_id`
|
||||
emailCols = []string{
|
||||
"id",
|
||||
"creation_date",
|
||||
@ -138,16 +140,16 @@ var (
|
||||
"email",
|
||||
"is_email_verified",
|
||||
}
|
||||
phoneQuery = `SELECT projections.users6.id,` +
|
||||
` projections.users6.creation_date,` +
|
||||
` projections.users6.change_date,` +
|
||||
` projections.users6.resource_owner,` +
|
||||
` projections.users6.sequence,` +
|
||||
` projections.users6_humans.user_id,` +
|
||||
` projections.users6_humans.phone,` +
|
||||
` projections.users6_humans.is_phone_verified` +
|
||||
` FROM projections.users6` +
|
||||
` LEFT JOIN projections.users6_humans ON projections.users6.id = projections.users6_humans.user_id AND projections.users6.instance_id = projections.users6_humans.instance_id`
|
||||
phoneQuery = `SELECT projections.users7.id,` +
|
||||
` projections.users7.creation_date,` +
|
||||
` projections.users7.change_date,` +
|
||||
` projections.users7.resource_owner,` +
|
||||
` projections.users7.sequence,` +
|
||||
` projections.users7_humans.user_id,` +
|
||||
` projections.users7_humans.phone,` +
|
||||
` projections.users7_humans.is_phone_verified` +
|
||||
` FROM projections.users7` +
|
||||
` LEFT JOIN projections.users7_humans ON projections.users7.id = projections.users7_humans.user_id AND projections.users7.instance_id = projections.users7_humans.instance_id`
|
||||
phoneCols = []string{
|
||||
"id",
|
||||
"creation_date",
|
||||
@ -158,14 +160,14 @@ var (
|
||||
"phone",
|
||||
"is_phone_verified",
|
||||
}
|
||||
userUniqueQuery = `SELECT projections.users6.id,` +
|
||||
` projections.users6.state,` +
|
||||
` projections.users6.username,` +
|
||||
` projections.users6_humans.user_id,` +
|
||||
` projections.users6_humans.email,` +
|
||||
` projections.users6_humans.is_email_verified` +
|
||||
` FROM projections.users6` +
|
||||
` LEFT JOIN projections.users6_humans ON projections.users6.id = projections.users6_humans.user_id AND projections.users6.instance_id = projections.users6_humans.instance_id`
|
||||
userUniqueQuery = `SELECT projections.users7.id,` +
|
||||
` projections.users7.state,` +
|
||||
` projections.users7.username,` +
|
||||
` projections.users7_humans.user_id,` +
|
||||
` projections.users7_humans.email,` +
|
||||
` projections.users7_humans.is_email_verified` +
|
||||
` FROM projections.users7` +
|
||||
` LEFT JOIN projections.users7_humans ON projections.users7.id = projections.users7_humans.user_id AND projections.users7.instance_id = projections.users7_humans.instance_id`
|
||||
userUniqueCols = []string{
|
||||
"id",
|
||||
"state",
|
||||
@ -174,40 +176,40 @@ var (
|
||||
"email",
|
||||
"is_email_verified",
|
||||
}
|
||||
notifyUserQuery = `SELECT projections.users6.id,` +
|
||||
` projections.users6.creation_date,` +
|
||||
` projections.users6.change_date,` +
|
||||
` projections.users6.resource_owner,` +
|
||||
` projections.users6.sequence,` +
|
||||
` projections.users6.state,` +
|
||||
` projections.users6.type,` +
|
||||
` projections.users6.username,` +
|
||||
notifyUserQuery = `SELECT projections.users7.id,` +
|
||||
` projections.users7.creation_date,` +
|
||||
` projections.users7.change_date,` +
|
||||
` projections.users7.resource_owner,` +
|
||||
` projections.users7.sequence,` +
|
||||
` projections.users7.state,` +
|
||||
` projections.users7.type,` +
|
||||
` projections.users7.username,` +
|
||||
` login_names.loginnames,` +
|
||||
` preferred_login_name.login_name,` +
|
||||
` projections.users6_humans.user_id,` +
|
||||
` projections.users6_humans.first_name,` +
|
||||
` projections.users6_humans.last_name,` +
|
||||
` projections.users6_humans.nick_name,` +
|
||||
` projections.users6_humans.display_name,` +
|
||||
` projections.users6_humans.preferred_language,` +
|
||||
` projections.users6_humans.gender,` +
|
||||
` projections.users6_humans.avatar_key,` +
|
||||
` projections.users6_notifications.user_id,` +
|
||||
` projections.users6_notifications.last_email,` +
|
||||
` projections.users6_notifications.verified_email,` +
|
||||
` projections.users6_notifications.last_phone,` +
|
||||
` projections.users6_notifications.verified_phone,` +
|
||||
` projections.users6_notifications.password_set,` +
|
||||
` projections.users7_humans.user_id,` +
|
||||
` projections.users7_humans.first_name,` +
|
||||
` projections.users7_humans.last_name,` +
|
||||
` projections.users7_humans.nick_name,` +
|
||||
` projections.users7_humans.display_name,` +
|
||||
` projections.users7_humans.preferred_language,` +
|
||||
` projections.users7_humans.gender,` +
|
||||
` projections.users7_humans.avatar_key,` +
|
||||
` projections.users7_notifications.user_id,` +
|
||||
` projections.users7_notifications.last_email,` +
|
||||
` projections.users7_notifications.verified_email,` +
|
||||
` projections.users7_notifications.last_phone,` +
|
||||
` projections.users7_notifications.verified_phone,` +
|
||||
` projections.users7_notifications.password_set,` +
|
||||
` COUNT(*) OVER ()` +
|
||||
` FROM projections.users6` +
|
||||
` LEFT JOIN projections.users6_humans ON projections.users6.id = projections.users6_humans.user_id AND projections.users6.instance_id = projections.users6_humans.instance_id` +
|
||||
` LEFT JOIN projections.users6_notifications ON projections.users6.id = projections.users6_notifications.user_id AND projections.users6.instance_id = projections.users6_notifications.instance_id` +
|
||||
` FROM projections.users7` +
|
||||
` LEFT JOIN projections.users7_humans ON projections.users7.id = projections.users7_humans.user_id AND projections.users7.instance_id = projections.users7_humans.instance_id` +
|
||||
` LEFT JOIN projections.users7_notifications ON projections.users7.id = projections.users7_notifications.user_id AND projections.users7.instance_id = projections.users7_notifications.instance_id` +
|
||||
` LEFT JOIN` +
|
||||
` (` + loginNamesQuery + `) AS login_names` +
|
||||
` ON login_names.user_id = projections.users6.id AND login_names.instance_id = projections.users6.instance_id` +
|
||||
` ON login_names.user_id = projections.users7.id AND login_names.instance_id = projections.users7.instance_id` +
|
||||
` LEFT JOIN` +
|
||||
` (` + preferredLoginNameQuery + `) AS preferred_login_name` +
|
||||
` ON preferred_login_name.user_id = projections.users6.id AND preferred_login_name.instance_id = projections.users6.instance_id`
|
||||
` ON preferred_login_name.user_id = projections.users7.id AND preferred_login_name.instance_id = projections.users7.instance_id`
|
||||
notifyUserCols = []string{
|
||||
"id",
|
||||
"creation_date",
|
||||
@ -237,41 +239,42 @@ var (
|
||||
"password_set",
|
||||
"count",
|
||||
}
|
||||
usersQuery = `SELECT projections.users6.id,` +
|
||||
` projections.users6.creation_date,` +
|
||||
` projections.users6.change_date,` +
|
||||
` projections.users6.resource_owner,` +
|
||||
` projections.users6.sequence,` +
|
||||
` projections.users6.state,` +
|
||||
` projections.users6.type,` +
|
||||
` projections.users6.username,` +
|
||||
usersQuery = `SELECT projections.users7.id,` +
|
||||
` projections.users7.creation_date,` +
|
||||
` projections.users7.change_date,` +
|
||||
` projections.users7.resource_owner,` +
|
||||
` projections.users7.sequence,` +
|
||||
` projections.users7.state,` +
|
||||
` projections.users7.type,` +
|
||||
` projections.users7.username,` +
|
||||
` login_names.loginnames,` +
|
||||
` preferred_login_name.login_name,` +
|
||||
` projections.users6_humans.user_id,` +
|
||||
` projections.users6_humans.first_name,` +
|
||||
` projections.users6_humans.last_name,` +
|
||||
` projections.users6_humans.nick_name,` +
|
||||
` projections.users6_humans.display_name,` +
|
||||
` projections.users6_humans.preferred_language,` +
|
||||
` projections.users6_humans.gender,` +
|
||||
` projections.users6_humans.avatar_key,` +
|
||||
` projections.users6_humans.email,` +
|
||||
` projections.users6_humans.is_email_verified,` +
|
||||
` projections.users6_humans.phone,` +
|
||||
` projections.users6_humans.is_phone_verified,` +
|
||||
` projections.users6_machines.user_id,` +
|
||||
` projections.users6_machines.name,` +
|
||||
` projections.users6_machines.description,` +
|
||||
` projections.users7_humans.user_id,` +
|
||||
` projections.users7_humans.first_name,` +
|
||||
` projections.users7_humans.last_name,` +
|
||||
` projections.users7_humans.nick_name,` +
|
||||
` projections.users7_humans.display_name,` +
|
||||
` projections.users7_humans.preferred_language,` +
|
||||
` projections.users7_humans.gender,` +
|
||||
` projections.users7_humans.avatar_key,` +
|
||||
` projections.users7_humans.email,` +
|
||||
` projections.users7_humans.is_email_verified,` +
|
||||
` projections.users7_humans.phone,` +
|
||||
` projections.users7_humans.is_phone_verified,` +
|
||||
` projections.users7_machines.user_id,` +
|
||||
` projections.users7_machines.name,` +
|
||||
` projections.users7_machines.description,` +
|
||||
` projections.users7_machines.has_secret,` +
|
||||
` COUNT(*) OVER ()` +
|
||||
` FROM projections.users6` +
|
||||
` LEFT JOIN projections.users6_humans ON projections.users6.id = projections.users6_humans.user_id AND projections.users6.instance_id = projections.users6_humans.instance_id` +
|
||||
` LEFT JOIN projections.users6_machines ON projections.users6.id = projections.users6_machines.user_id AND projections.users6.instance_id = projections.users6_machines.instance_id` +
|
||||
` FROM projections.users7` +
|
||||
` LEFT JOIN projections.users7_humans ON projections.users7.id = projections.users7_humans.user_id AND projections.users7.instance_id = projections.users7_humans.instance_id` +
|
||||
` LEFT JOIN projections.users7_machines ON projections.users7.id = projections.users7_machines.user_id AND projections.users7.instance_id = projections.users7_machines.instance_id` +
|
||||
` LEFT JOIN` +
|
||||
` (` + loginNamesQuery + `) AS login_names` +
|
||||
` ON login_names.user_id = projections.users6.id AND login_names.instance_id = projections.users6.instance_id` +
|
||||
` ON login_names.user_id = projections.users7.id AND login_names.instance_id = projections.users7.instance_id` +
|
||||
` LEFT JOIN` +
|
||||
` (` + preferredLoginNameQuery + `) AS preferred_login_name` +
|
||||
` ON preferred_login_name.user_id = projections.users6.id AND preferred_login_name.instance_id = projections.users6.instance_id`
|
||||
` ON preferred_login_name.user_id = projections.users7.id AND preferred_login_name.instance_id = projections.users7.instance_id`
|
||||
usersCols = []string{
|
||||
"id",
|
||||
"creation_date",
|
||||
@ -300,6 +303,7 @@ var (
|
||||
"user_id",
|
||||
"name",
|
||||
"description",
|
||||
"has_secret",
|
||||
"count",
|
||||
}
|
||||
)
|
||||
@ -372,6 +376,7 @@ func Test_UserPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
1,
|
||||
},
|
||||
),
|
||||
@ -439,6 +444,7 @@ func Test_UserPrepares(t *testing.T) {
|
||||
"id",
|
||||
"name",
|
||||
"description",
|
||||
true,
|
||||
1,
|
||||
},
|
||||
),
|
||||
@ -457,6 +463,7 @@ func Test_UserPrepares(t *testing.T) {
|
||||
Machine: &Machine{
|
||||
Name: "name",
|
||||
Description: "description",
|
||||
HasSecret: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -1036,6 +1043,7 @@ func Test_UserPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
},
|
||||
),
|
||||
@ -1111,6 +1119,7 @@ func Test_UserPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"id",
|
||||
@ -1140,6 +1149,7 @@ func Test_UserPrepares(t *testing.T) {
|
||||
"id",
|
||||
"name",
|
||||
"description",
|
||||
true,
|
||||
},
|
||||
},
|
||||
),
|
||||
@ -1188,6 +1198,7 @@ func Test_UserPrepares(t *testing.T) {
|
||||
Machine: &Machine{
|
||||
Name: "name",
|
||||
Description: "description",
|
||||
HasSecret: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -114,5 +114,9 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
|
||||
RegisterFilterEventMapper(AggregateType, MachineKeyAddedEventType, MachineKeyAddedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, MachineKeyRemovedEventType, MachineKeyRemovedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, PersonalAccessTokenAddedType, PersonalAccessTokenAddedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, PersonalAccessTokenRemovedType, PersonalAccessTokenRemovedEventMapper)
|
||||
RegisterFilterEventMapper(AggregateType, PersonalAccessTokenRemovedType, PersonalAccessTokenRemovedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, MachineSecretSetType, MachineSecretSetEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, MachineSecretRemovedType, MachineSecretRemovedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, MachineSecretCheckSucceededType, MachineSecretCheckSucceededEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, MachineSecretCheckFailedType, MachineSecretCheckFailedEventMapper)
|
||||
}
|
||||
|
171
internal/repository/user/machine_secret.go
Normal file
171
internal/repository/user/machine_secret.go
Normal file
@ -0,0 +1,171 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/repository"
|
||||
)
|
||||
|
||||
const (
|
||||
machineSecretPrefix = machineEventPrefix + "secret."
|
||||
MachineSecretSetType = machineSecretPrefix + "set"
|
||||
MachineSecretRemovedType = machineSecretPrefix + "removed"
|
||||
MachineSecretCheckSucceededType = machineSecretPrefix + "check.succeeded"
|
||||
MachineSecretCheckFailedType = machineSecretPrefix + "check.failed"
|
||||
)
|
||||
|
||||
type MachineSecretSetEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
|
||||
ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"`
|
||||
}
|
||||
|
||||
func (e *MachineSecretSetEvent) Data() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *MachineSecretSetEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewMachineSecretSetEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
clientSecret *crypto.CryptoValue,
|
||||
) *MachineSecretSetEvent {
|
||||
return &MachineSecretSetEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
MachineSecretSetType,
|
||||
),
|
||||
ClientSecret: clientSecret,
|
||||
}
|
||||
}
|
||||
|
||||
func MachineSecretSetEventMapper(event *repository.Event) (eventstore.Event, error) {
|
||||
credentialsSet := &MachineSecretSetEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}
|
||||
err := json.Unmarshal(event.Data, credentialsSet)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "USER-lopbqu", "unable to unmarshal machine secret set")
|
||||
}
|
||||
|
||||
return credentialsSet, nil
|
||||
}
|
||||
|
||||
type MachineSecretRemovedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
}
|
||||
|
||||
func (e *MachineSecretRemovedEvent) Data() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *MachineSecretRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewMachineSecretRemovedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
) *MachineSecretRemovedEvent {
|
||||
return &MachineSecretRemovedEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
MachineSecretRemovedType,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func MachineSecretRemovedEventMapper(event *repository.Event) (eventstore.Event, error) {
|
||||
credentialsRemoved := &MachineSecretRemovedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}
|
||||
err := json.Unmarshal(event.Data, credentialsRemoved)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "USER-quox9j2", "unable to unmarshal machine secret removed")
|
||||
}
|
||||
|
||||
return credentialsRemoved, nil
|
||||
}
|
||||
|
||||
type MachineSecretCheckSucceededEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
}
|
||||
|
||||
func (e *MachineSecretCheckSucceededEvent) Data() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *MachineSecretCheckSucceededEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewMachineSecretCheckSucceededEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
) *MachineSecretCheckSucceededEvent {
|
||||
return &MachineSecretCheckSucceededEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
MachineSecretCheckSucceededType,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func MachineSecretCheckSucceededEventMapper(event *repository.Event) (eventstore.Event, error) {
|
||||
check := &MachineSecretCheckSucceededEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}
|
||||
err := json.Unmarshal(event.Data, check)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "USER-x002n1p", "unable to unmarshal machine secret check succeeded")
|
||||
}
|
||||
|
||||
return check, nil
|
||||
}
|
||||
|
||||
type MachineSecretCheckFailedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
}
|
||||
|
||||
func (e *MachineSecretCheckFailedEvent) Data() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *MachineSecretCheckFailedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewMachineSecretCheckFailedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
) *MachineSecretCheckFailedEvent {
|
||||
return &MachineSecretCheckFailedEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
MachineSecretCheckFailedType,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func MachineSecretCheckFailedEventMapper(event *repository.Event) (eventstore.Event, error) {
|
||||
check := &MachineSecretCheckFailedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}
|
||||
err := json.Unmarshal(event.Data, check)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "USER-x7901b1l", "unable to unmarshal machine secret check failed")
|
||||
}
|
||||
|
||||
return check, nil
|
||||
}
|
@ -87,6 +87,10 @@ Errors:
|
||||
Key:
|
||||
NotFound: Maschinen Schlüssel nicht gefunden
|
||||
AlreadyExisting: Machine Schlüssel exisiert bereits
|
||||
Secret:
|
||||
NotExisting: Secret existiert nicht
|
||||
Invalid: Secret ist ungültig
|
||||
CouldNotGenerate: Secret konnte nicht generiert werden
|
||||
PAT:
|
||||
NotFound: Persönliches Access Token nicht gefunden
|
||||
NotHuman: Der Benutzer muss eine Person sein
|
||||
@ -475,8 +479,14 @@ EventTypes:
|
||||
added: Technischer Benutzer hinzugefügt
|
||||
changed: Technischer Benutzer geändert
|
||||
key:
|
||||
added: Key added
|
||||
removed: Key removed
|
||||
added: Key hinzugefügt
|
||||
removed: Key entfernt
|
||||
secret:
|
||||
set: Secret gesetzt
|
||||
removed: Secret entfernt
|
||||
check:
|
||||
succeeded: Secret Überprüfung erfolgreich
|
||||
failed: Secret Überprüfung fehlgeschlagen
|
||||
human:
|
||||
added: Benutzer hinzugefügt
|
||||
selfregistered: Benutzer hat sich selbst registriert
|
||||
|
@ -87,6 +87,10 @@ Errors:
|
||||
Key:
|
||||
NotFound: Machine key not found
|
||||
AlreadyExisting: Machine key already existing
|
||||
Secret:
|
||||
NotExisting: Secret doesn't exist
|
||||
Invalid: Secret is invalid
|
||||
CouldNotGenerate: Secret could not be generated
|
||||
PAT:
|
||||
NotFound: Personal Access Token not found
|
||||
NotHuman: The User must be personal
|
||||
@ -477,6 +481,12 @@ EventTypes:
|
||||
key:
|
||||
added: Key added
|
||||
removed: Key removed
|
||||
secret:
|
||||
set: Secret set
|
||||
removed: Secret removed
|
||||
check:
|
||||
succeeded: Secret check succeeded
|
||||
failed: Secret check failed
|
||||
human:
|
||||
added: Person added
|
||||
selfregistered: Person registered himself
|
||||
|
@ -87,6 +87,10 @@ Errors:
|
||||
Key:
|
||||
NotFound: Clé de la machine non trouvée
|
||||
AlreadyExisting: Clé de la machine déjà existante
|
||||
Secret:
|
||||
NotExisting: Secret n'existe pas
|
||||
Invalid: Secret n'est pas valide
|
||||
CouldNotGenerate: Secret n'a pas pu être généré
|
||||
PAT:
|
||||
NotFound: Token d'accès personnel non trouvé
|
||||
NotHuman: L'utilisateur doit être personnel
|
||||
@ -475,6 +479,12 @@ EventTypes:
|
||||
key:
|
||||
added: Clé ajoutée
|
||||
removed: Clé supprimée
|
||||
secret:
|
||||
set: Secret défini
|
||||
removed: Secret supprimée
|
||||
check:
|
||||
succeeded: La vérification de Secret réussie
|
||||
failed: La vérification de Secret a échoué
|
||||
human:
|
||||
added: Personne ajoutée
|
||||
selfregistered: La personne s'est enregistrée elle-même
|
||||
|
@ -87,6 +87,10 @@ Errors:
|
||||
Key:
|
||||
NotFound: Chiave macchina non trovato
|
||||
AlreadyExisting: Chiave macchina già esistente
|
||||
Secret:
|
||||
NotExisting: Secret non esiste
|
||||
Invalid: Secret non è valido
|
||||
CouldNotGenerate: Non è stato possibile generare il Secret
|
||||
PAT:
|
||||
NotFound: Personal Access Token non trovato
|
||||
NotHuman: L'utente deve essere personale
|
||||
@ -475,6 +479,12 @@ EventTypes:
|
||||
key:
|
||||
added: Chiave aggiunta
|
||||
removed: Chiave rimossa
|
||||
secret:
|
||||
set: Secret set
|
||||
removed: Secret rimosso
|
||||
check:
|
||||
succeeded: Controllo della Secret riuscito
|
||||
failed: Controllo della Secret fallito
|
||||
human:
|
||||
added: Persona aggiunta
|
||||
selfregistered: Persona registrata
|
||||
|
@ -87,6 +87,10 @@ Errors:
|
||||
Key:
|
||||
NotFound: 未找到机器密钥
|
||||
AlreadyExisting: 已有的机器钥匙
|
||||
Secret:
|
||||
NotExisting: 秘密并不存在
|
||||
Invalid: 秘密是无效的
|
||||
CouldNotGenerate: 无法生成秘密
|
||||
PAT:
|
||||
NotFound: 未找到个人访问令牌
|
||||
NotHuman: 用户必须是个人
|
||||
@ -465,6 +469,12 @@ EventTypes:
|
||||
key:
|
||||
added: 添加服务用户 Key
|
||||
removed: 删除服务用户 Key
|
||||
secret:
|
||||
set: 秘密套装
|
||||
removed: 秘密删除
|
||||
check:
|
||||
succeeded: 成功的秘密控制
|
||||
failed: 秘密控制失败
|
||||
human:
|
||||
added: 添加用户
|
||||
selfregistered: 自注册用户
|
||||
|
@ -625,6 +625,29 @@ service ManagementService {
|
||||
};
|
||||
}
|
||||
|
||||
// Generates and sets a new machine secret
|
||||
rpc GenerateMachineSecret(GenerateMachineSecretRequest) returns (GenerateMachineSecretResponse) {
|
||||
option (google.api.http) = {
|
||||
put: "/users/{user_id}/secret"
|
||||
body: "*"
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "user.write"
|
||||
};
|
||||
}
|
||||
|
||||
// Removes the machine secret
|
||||
rpc RemoveMachineSecret(RemoveMachineSecretRequest) returns (RemoveMachineSecretResponse) {
|
||||
option (google.api.http) = {
|
||||
delete: "/users/{user_id}/secret"
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "user.write"
|
||||
};
|
||||
}
|
||||
|
||||
// Returns a machine key of a (machine) user
|
||||
rpc GetMachineKeyByIDs(GetMachineKeyByIDsRequest) returns (GetMachineKeyByIDsResponse) {
|
||||
option (google.api.http) = {
|
||||
@ -3616,6 +3639,24 @@ message UpdateMachineResponse {
|
||||
zitadel.v1.ObjectDetails details = 1;
|
||||
}
|
||||
|
||||
message GenerateMachineSecretRequest {
|
||||
string user_id = 1 [(validate.rules).string.min_len = 1];
|
||||
}
|
||||
|
||||
message GenerateMachineSecretResponse {
|
||||
string client_id = 1;
|
||||
string client_secret = 2;
|
||||
zitadel.v1.ObjectDetails details = 3;
|
||||
}
|
||||
|
||||
message RemoveMachineSecretRequest {
|
||||
string user_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
}
|
||||
|
||||
message RemoveMachineSecretResponse {
|
||||
zitadel.v1.ObjectDetails details = 1;
|
||||
}
|
||||
|
||||
message GetMachineKeyByIDsRequest {
|
||||
string user_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
string key_id = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
|
@ -78,6 +78,11 @@ message Machine {
|
||||
example: "\"The one and only IAM\"";
|
||||
}
|
||||
];
|
||||
bool has_secret = 3 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"true\"";
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
message Profile {
|
||||
|
Loading…
x
Reference in New Issue
Block a user