Merge branch 'main' into prompt-to-business-slice-alloc-fix

This commit is contained in:
Tim Möhlmann 2024-12-04 12:30:17 +02:00 committed by GitHub
commit aa5fb5dc2d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
45 changed files with 642 additions and 251 deletions

View File

@ -12,6 +12,7 @@ If you are using Zitadel, please consider adding yourself as a user with a quick
| ----------------------- | -------------------------------------------------------------------- | ----------------------------------------------- |
| Zitadel | [@fforootd](https://github.com/fforootd) (and many more) | Zitadel Cloud makes heavy use of of Zitadel ;-) |
| Rawkode Academy | [@RawkodeAcademy](https://github.com/RawkodeAcademy) | Rawkode Academy Platform & Zulip use Zitadel for all user and M2M authentication |
| devOS: Sanity Edition | [@devOS-Sanity-Edition](https://github.com/devOS-Sanity-Edition) | Uses SSO Auth for every piece of our internal and external infrastructure |
| CNAP.tech | [@cnap-tech](https://github.com/cnap-tech) | Using Zitadel for authentication and authorization in cloud-native applications |
| Minekube | [@minekube](https://github.com/minekube) | Leveraging Zitadel for secure user authentication in gaming infrastructure |
| Organization Name | contact@example.com | Description of how they use Zitadel |

View File

@ -1722,8 +1722,8 @@
"ALLOWUSERNAMEPASSWORD_DESC": "Разрешено е конвенционалното влизане с потребителско име и парола.",
"ALLOWEXTERNALIDP_DESC": "Входът е разрешен за основните доставчици на самоличност",
"ALLOWREGISTER_DESC": "Ако опцията е избрана, в входа се появява допълнителна стъпка за регистрация на потребител.",
"FORCEMFA": "Сила MFA",
"FORCEMFALOCALONLY": "Принудително MFA за локални потребители",
"FORCEMFA": "Наложи MFA за всички потребители",
"FORCEMFALOCALONLY": "Наложи MFA само за локално автентифицирани потребители",
"FORCEMFALOCALONLY_DESC": "Ако е избрана опцията, локалните удостоверени потребители трябва да конфигурират втори фактор за влизане.",
"HIDEPASSWORDRESET_DESC": "Ако опцията е избрана, потребителят не може да нулира паролата си в процеса на влизане.",
"HIDELOGINNAMESUFFIX": "Скриване на суфикса на името за влизане",

View File

@ -1724,8 +1724,8 @@
"ALLOWUSERNAMEPASSWORD_DESC": "Je povoleno klasické přihlášení s uživatelským jménem a heslem.",
"ALLOWEXTERNALIDP_DESC": "Přihlášení je povoleno pro níže uvedené poskytovatele identity.",
"ALLOWREGISTER_DESC": "Pokud je možnost vybrána, objeví se při přihlášení další krok pro registraci uživatele.",
"FORCEMFA": "Vynutit MFA",
"FORCEMFALOCALONLY": "Vynutit MFA pouze pro lokálně ověřené uživatele",
"FORCEMFA": "Vynuti MFA pro všechny uživatele",
"FORCEMFALOCALONLY": "Vynutit MFA pouze pro místně ověřené uživatele",
"FORCEMFALOCALONLY_DESC": "Pokud je možnost vybrána, lokálně ověření uživatelé musí pro přihlášení nastavit druhý faktor.",
"HIDEPASSWORDRESET_DESC": "Pokud je možnost vybrána, uživatel nemůže během přihlašovacího procesu resetovat své heslo.",
"HIDELOGINNAMESUFFIX": "Skrýt příponu přihlašovacího jména",

View File

@ -1723,8 +1723,8 @@
"ALLOWUSERNAMEPASSWORD_DESC": "Der konventionelle Login mit Benutzername und Passwort wird erlaubt.",
"ALLOWEXTERNALIDP_DESC": "Der Login wird für die darunter liegenden Identitätsanbieter erlaubt.",
"ALLOWREGISTER_DESC": "Ist die Option gewählt, erscheint im Login ein zusätzlicher Schritt zum Registrieren eines Benutzers.",
"FORCEMFA": "MFA erzwingen",
"FORCEMFALOCALONLY": "MFA für lokale Benutzer erzwingen",
"FORCEMFA": "MFA für alle Benutzer erzwingen",
"FORCEMFALOCALONLY": "MFA nur für lokal authentifizierte Benutzer erzwingen",
"FORCEMFALOCALONLY_DESC": "Ist die Option gewählt, müssen lokal authentifizierte Benutzer einen zweiten Faktor für den Login verwenden.",
"HIDEPASSWORDRESET_DESC": "Ist die Option gewählt, ist es nicht möglich im Login das Passwort zurück zusetzen via Passwort vergessen Link.",
"HIDELOGINNAMESUFFIX": "Loginname Suffix ausblenden",

View File

@ -1723,8 +1723,8 @@
"ALLOWUSERNAMEPASSWORD_DESC": "The conventional login with user name and password is allowed.",
"ALLOWEXTERNALIDP_DESC": "The login is allowed for the underlying identity providers",
"ALLOWREGISTER_DESC": "If the option is selected, an additional step for registering a user appears in the login.",
"FORCEMFA": "Force MFA",
"FORCEMFALOCALONLY": "Force MFA for local authenticated users",
"FORCEMFA": "Force MFA for all users",
"FORCEMFALOCALONLY": "Force MFA for local authenticated users only",
"FORCEMFALOCALONLY_DESC": "If the option is selected, local authenticated users have to configure a second factor for login.",
"HIDEPASSWORDRESET_DESC": "If the option is selected, the user can't reset his password in the login process.",
"HIDELOGINNAMESUFFIX": "Hide Loginname suffix",

View File

@ -1724,8 +1724,8 @@
"ALLOWUSERNAMEPASSWORD_DESC": "El inicio de sesión convencional con nombre de usuario y contraseña está permitido.",
"ALLOWEXTERNALIDP_DESC": "El inicio de sesión está permitido para los proveedores de identidad subyacentes",
"ALLOWREGISTER_DESC": "Si esta opción es seleccionada, aparece un paso adicional durante el inicio de sesión para registrar un usuario.",
"FORCEMFA": "Forzar MFA",
"FORCEMFALOCALONLY": "Forzar MFA para usuarios locales",
"FORCEMFA": "Forzar MFA para todos los usuarios",
"FORCEMFALOCALONLY": "Forzar MFA solo para usuarios autenticados localmente",
"FORCEMFALOCALONLY_DESC": "Si esta opción es seleccionada, los usuarios autenticados localmente tendrán que configurar un doble factor para iniciar sesión",
"HIDEPASSWORDRESET_DESC": "Si esta opción es seleccionada, el usuario no podrá restablecer su contraseña en el proceso de inicio de sesión.",
"HIDELOGINNAMESUFFIX": "Ocultar sufijo del nombre de inicio de sesión",

View File

@ -1723,8 +1723,8 @@
"ALLOWUSERNAMEPASSWORD_DESC": "La connexion classique avec nom d'utilisateur et mot de passe est autorisée.",
"ALLOWEXTERNALIDP_DESC": "La connexion est autorisée pour les fournisseurs d'identité sous-jacents",
"ALLOWREGISTER_DESC": "Si l'option est sélectionnée, une étape supplémentaire pour l'enregistrement d'un utilisateur apparaît dans la connexion.",
"FORCEMFA": "Forcer MFA",
"FORCEMFALOCALONLY": "Forcer MFA pour les utilisateurs locaux",
"FORCEMFA": "Forcer MFA pour tous les utilisateurs",
"FORCEMFALOCALONLY": "Forcer MFA uniquement pour les utilisateurs authentifiés localement",
"FORCEMFALOCALONLY_DESC": "Si l'option est sélectionnée, les utilisateurs locaux authentifiés doivent configurer un deuxième facteur pour la connexion.",
"HIDEPASSWORDRESET_DESC": "Si l'option est sélectionnée, l'utilisateur ne peut pas réinitialiser son mot de passe lors du processus de connexion.",
"HIDELOGINNAMESUFFIX": "Masquer le suffixe du nom de connexion",

View File

@ -1721,8 +1721,8 @@
"ALLOWUSERNAMEPASSWORD_DESC": "A hagyományos bejelentkezés felhasználónévvel és jelszóval engedélyezett.",
"ALLOWEXTERNALIDP_DESC": "A bejelentkezés engedélyezett az alapul szolgáló identitásszolgáltatóknál",
"ALLOWREGISTER_DESC": "Ha ezt az opciót választod, egy további lépés jelenik meg a bejelentkezés során a felhasználói regisztrációhoz.",
"FORCEMFA": "MFA kényszerítése",
"FORCEMFALOCALONLY": "Kényszerítsd az MFA-t a helyileg hitelesített felhasználókra",
"FORCEMFA": "MFA kikényszerítése minden felhasználó számára",
"FORCEMFALOCALONLY": "MFA kikényszerítése csak helyi hitelesített felhasználók számára",
"FORCEMFALOCALONLY_DESC": "Ha ezt az opciót választod, a helyileg hitelesített felhasználóknak be kell állítaniuk egy második faktor a bejelentkezéshez.",
"HIDEPASSWORDRESET_DESC": "Ha ezt az opciót választod, a felhasználó nem tudja visszaállítani a jelszavát a bejelentkezési folyamat során.",
"HIDELOGINNAMESUFFIX": "Bejelentkezési név utótag elrejtése",

View File

@ -1587,8 +1587,8 @@
"ALLOWUSERNAMEPASSWORD_DESC": "Login konvensional dengan nama pengguna dan kata sandi diperbolehkan.",
"ALLOWEXTERNALIDP_DESC": "Login diperbolehkan untuk penyedia identitas yang mendasarinya",
"ALLOWREGISTER_DESC": "Jika opsi ini dipilih, langkah tambahan untuk mendaftarkan pengguna akan muncul di login.",
"FORCEMFA": "Paksa MFA",
"FORCEMFALOCALONLY": "Paksa MFA untuk pengguna lokal yang diautentikasi",
"FORCEMFA": "Memaksa MFA untuk semua pengguna",
"FORCEMFALOCALONLY": "Memaksa MFA hanya untuk pengguna yang diautentikasi lokal",
"FORCEMFALOCALONLY_DESC": "Jika opsi ini dipilih, pengguna yang diautentikasi lokal harus mengonfigurasi faktor kedua untuk login.",
"HIDEPASSWORDRESET_DESC": "Jika opsi ini dipilih, pengguna tidak dapat mengatur ulang kata sandinya dalam proses login.",
"HIDELOGINNAMESUFFIX": "Sembunyikan akhiran Nama Login",

View File

@ -1723,8 +1723,8 @@
"ALLOWUSERNAMEPASSWORD_DESC": "Autenticazione classica con nome utente e password è permessa.",
"ALLOWEXTERNALIDP_DESC": "Il login è permesso per gli IDP sottostanti",
"ALLOWREGISTER_DESC": "Se l'opzione è selezionata, nel login apparirà un passo aggiuntivo per la registrazione di un utente.",
"FORCEMFA": "Forza MFA",
"FORCEMFALOCALONLY": "Forza MFA per gli utenti locali",
"FORCEMFA": "Forzare MFA per tutti gli utenti",
"FORCEMFALOCALONLY": "Forzare MFA solo per gli utenti autenticati localmente",
"FORCEMFALOCALONLY_DESC": "Se l'opzione è selezionata, gli utenti locali autenticati devono configurare un secondo fattore per l'accesso.",
"HIDEPASSWORDRESET_DESC": "Se l'opzione è selezionata, l'utente non può resettare la sua password nel interfaccia login.",
"HIDELOGINNAMESUFFIX": "Nascondi il suffisso del nome utente",

View File

@ -1718,8 +1718,8 @@
"ALLOWUSERNAMEPASSWORD_DESC": "ユーザー名とパスワードを使用した従来のログインを許可します。",
"ALLOWEXTERNALIDP_DESC": "基礎となるIDプロバイダーにログインを許可します。",
"ALLOWREGISTER_DESC": "このオプションが選択されている場合、ユーザーを登録するための追加のステップがログインに表示されます。",
"FORCEMFA": "MFAを強制する",
"FORCEMFALOCALONLY": "ローカル ユーザーに MFA を強制する",
"FORCEMFA": "すべてのユーザーに MFA を強制する",
"FORCEMFALOCALONLY": "ローカル認証ユーザーのみに MFA を強制する",
"FORCEMFALOCALONLY_DESC": "オプションが選択されている場合、ローカル認証されたユーザーはログインの 2 番目の要素を構成する必要があります。",
"HIDEPASSWORDRESET_DESC": "このオプションが選択されている場合、ユーザーはログイン過程ででパスワードをリセットできません。",
"HIDELOGINNAMESUFFIX": "ログイン名の接尾辞を非表示にする",

View File

@ -1724,8 +1724,8 @@
"ALLOWUSERNAMEPASSWORD_DESC": "Дозволена е конвенционална најава со корисничко име и лозинка.",
"ALLOWEXTERNALIDP_DESC": "Најавата е дозволена за поддржуваните IDPs",
"ALLOWREGISTER_DESC": "Доколку е избрана опцијата, се прикажува дополнителен чекор за регистрирање на корисник во најавата.",
"FORCEMFA": "Задолжителна MFA",
"FORCEMFALOCALONLY": "Force MFA за локални корисници",
"FORCEMFA": "Наметнете MFA за сите корисници",
"FORCEMFALOCALONLY": "Наметнете MFA само за локално автентифицирани корисници",
"FORCEMFALOCALONLY_DESC": "Ако е избрана опцијата, локалните автентицирани корисници треба да конфигурираат втор фактор за најавување.",
"HIDEPASSWORDRESET_DESC": "Доколку е избрана опцијата, корисникот нема да може да ја ресетира својата лозинка во процесот на најава.",
"HIDELOGINNAMESUFFIX": "Сокриј го суфиксот на корисничкото име",

View File

@ -1721,8 +1721,8 @@
"ALLOWUSERNAMEPASSWORD_DESC": "De conventionele login met gebruikersnaam en wachtwoord is toegestaan.",
"ALLOWEXTERNALIDP_DESC": "De login is toegestaan voor de onderliggende identiteitsproviders",
"ALLOWREGISTER_DESC": "Als de optie is geselecteerd, verschijnt er een extra stap voor het registreren van een gebruiker in het login proces.",
"FORCEMFA": "Forceer MFA",
"FORCEMFALOCALONLY": "Forceer MFA voor lokaal geauthenticeerde gebruikers",
"FORCEMFA": "MFA afdwingen voor alle gebruikers",
"FORCEMFALOCALONLY": "MFA alleen afdwingen voor lokaal geverifieerde gebruikers",
"FORCEMFALOCALONLY_DESC": "Als de optie is geselecteerd, moeten lokaal geauthenticeerde gebruikers een tweede factor configureren voor login.",
"HIDEPASSWORDRESET_DESC": "Als de optie is geselecteerd, kan de gebruiker zijn wachtwoord niet resetten in het login proces.",
"HIDELOGINNAMESUFFIX": "Verberg Inlognaam achtervoegsel",

View File

@ -1722,8 +1722,8 @@
"ALLOWUSERNAMEPASSWORD_DESC": "Zwykłe logowanie za pomocą nazwy użytkownika i hasła jest dozwolone.",
"ALLOWEXTERNALIDP_DESC": "Logowanie jest dozwolone dla dostawców tożsamości podstawowych",
"ALLOWREGISTER_DESC": "Jeśli ta opcja jest zaznaczona, pojawi się dodatkowy krok rejestracji użytkownika w procesie logowania.",
"FORCEMFA": "Wymuś MFA",
"FORCEMFALOCALONLY": "Wymuś MFA dla lokalnych użytkowników",
"FORCEMFA": "Wymuś MFA dla wszystkich użytkowników",
"FORCEMFALOCALONLY": "Wymuś MFA tylko dla lokalnie uwierzytelnionych użytkowników",
"FORCEMFALOCALONLY_DESC": "Jeśli ta opcja jest zaznaczona, lokalni uwierzytelnieni użytkownicy muszą skonfigurować drugi czynnik logowania.",
"HIDEPASSWORDRESET_DESC": "Jeśli ta opcja jest zaznaczona, użytkownik nie może zresetować swojego hasła w procesie logowania.",
"HIDELOGINNAMESUFFIX": "Ukryj sufiks nazwy użytkownika",

View File

@ -1724,7 +1724,8 @@
"ALLOWUSERNAMEPASSWORD_DESC": "O login convencional com nome de usuário e senha é permitido.",
"ALLOWEXTERNALIDP_DESC": "O login é permitido para os provedores de identidade subjacentes",
"ALLOWREGISTER_DESC": "Se a opção estiver selecionada, uma etapa adicional para registrar um usuário aparecerá no login.",
"FORCEMFA": "Forçar MFA",
"FORCEMFA": "Forçar MFA para todos os utilizadores",
"FORCEMFALOCALONLY": "Forçar MFA apenas para utilizadores autenticados localmente",
"HIDEPASSWORDRESET_DESC": "Se a opção estiver selecionada, o usuário não poderá redefinir sua senha no processo de login.",
"HIDELOGINNAMESUFFIX": "Ocultar sufixo do nome de login",
"HIDELOGINNAMESUFFIX_DESC": "Oculta o sufixo do nome de login na interface de login",

View File

@ -1794,7 +1794,8 @@
"ALLOWUSERNAMEPASSWORD_DESC": "Допускается стандартный вход с именем пользователя и паролем.",
"ALLOWEXTERNALIDP_DESC": "Вход разрешён для основных поставщиков идентификационных данных.",
"ALLOWREGISTER_DESC": "Если данный параметр выбран, при входе в систему появляется дополнительный шаг для регистрации пользователя.",
"FORCEMFA": "Принудительная многофакторная аутентификация (MFA)",
"FORCEMFA": "Принудительная многофакторная аутентификация для всех пользователей",
"FORCEMFALOCALONLY": "Принудительная многофакторная аутентификация только для локально аутентифицированных пользователей",
"FORCEMFA_DESC": "Если данный параметр выбран, пользователи должны настроить двухфакторную аутентификацию для входа в систему.",
"HIDEPASSWORDRESET": "Скрыть сброс пароля",
"HIDEPASSWORDRESET_DESC": "Если данный параметр выбран, пользователь не может сбросить свой пароль в процессе входа в систему.",

View File

@ -1727,8 +1727,8 @@
"ALLOWUSERNAMEPASSWORD_DESC": "Den konventionella inloggningen med användarnamn och lösenord är tillåten.",
"ALLOWEXTERNALIDP_DESC": "Inloggning är tillåten för de underliggande identitetsleverantörerna",
"ALLOWREGISTER_DESC": "Om alternativet är valt visas ett ytterligare steg för att registrera en användare i inloggningen.",
"FORCEMFA": "Tvinga MFA",
"FORCEMFALOCALONLY": "Tvinga MFA för lokalt autentiserade användare",
"FORCEMFA": "Tvinga MFA för alla användare",
"FORCEMFALOCALONLY": "Tvinga MFA endast för lokalt autentiserade användare",
"FORCEMFALOCALONLY_DESC": "Om alternativet är valt måste lokalt autentiserade användare konfigurera en andra faktor för inloggning.",
"HIDEPASSWORDRESET_DESC": "Om alternativet är valt kan användaren inte återställa sitt lösenord i inloggningsprocessen.",
"HIDELOGINNAMESUFFIX": "Dölj inloggningsnamn suffix",

View File

@ -1722,8 +1722,8 @@
"ALLOWUSERNAMEPASSWORD_DESC": "允许使用用户名和密码进行登录。",
"ALLOWEXTERNALIDP_DESC": "允许外部身份提供者进行登录",
"ALLOWREGISTER_DESC": "如果选择了该选项,登录中会出现一个用于注册用户的附加步骤。",
"FORCEMFA": "强制使用 MFA",
"FORCEMFALOCALONLY": "对本地用户强制执行 MFA",
"FORCEMFA": "强制所有用户使用 MFA",
"FORCEMFALOCALONLY": "仅强制本地认证用户使用 MFA",
"FORCEMFALOCALONLY_DESC": "如果选择该选项,本地经过身份验证的用户必须配置第二个登录因素。",
"HIDEPASSWORDRESET_DESC": "如果选择该选项,则用户无法在登录过程中重置其密码。",
"HIDELOGINNAMESUFFIX": "隐藏登录名后缀",

View File

@ -469,12 +469,12 @@ func updateAppleProviderToCommand(req *admin_pb.UpdateAppleProviderRequest) comm
}
}
func addSAMLProviderToCommand(req *admin_pb.AddSAMLProviderRequest) command.SAMLProvider {
func addSAMLProviderToCommand(req *admin_pb.AddSAMLProviderRequest) *command.SAMLProvider {
var nameIDFormat *domain.SAMLNameIDFormat
if req.NameIdFormat != nil {
nameIDFormat = gu.Ptr(idp_grpc.SAMLNameIDFormatToDomain(req.GetNameIdFormat()))
}
return command.SAMLProvider{
return &command.SAMLProvider{
Name: req.Name,
Metadata: req.GetMetadataXml(),
MetadataURL: req.GetMetadataUrl(),
@ -486,12 +486,12 @@ func addSAMLProviderToCommand(req *admin_pb.AddSAMLProviderRequest) command.SAML
}
}
func updateSAMLProviderToCommand(req *admin_pb.UpdateSAMLProviderRequest) command.SAMLProvider {
func updateSAMLProviderToCommand(req *admin_pb.UpdateSAMLProviderRequest) *command.SAMLProvider {
var nameIDFormat *domain.SAMLNameIDFormat
if req.NameIdFormat != nil {
nameIDFormat = gu.Ptr(idp_grpc.SAMLNameIDFormatToDomain(req.GetNameIdFormat()))
}
return command.SAMLProvider{
return &command.SAMLProvider{
Name: req.Name,
Metadata: req.GetMetadataXml(),
MetadataURL: req.GetMetadataUrl(),

View File

@ -462,12 +462,12 @@ func updateAppleProviderToCommand(req *mgmt_pb.UpdateAppleProviderRequest) comma
}
}
func addSAMLProviderToCommand(req *mgmt_pb.AddSAMLProviderRequest) command.SAMLProvider {
func addSAMLProviderToCommand(req *mgmt_pb.AddSAMLProviderRequest) *command.SAMLProvider {
var nameIDFormat *domain.SAMLNameIDFormat
if req.NameIdFormat != nil {
nameIDFormat = gu.Ptr(idp_grpc.SAMLNameIDFormatToDomain(req.GetNameIdFormat()))
}
return command.SAMLProvider{
return &command.SAMLProvider{
Name: req.Name,
Metadata: req.GetMetadataXml(),
MetadataURL: req.GetMetadataUrl(),
@ -479,12 +479,12 @@ func addSAMLProviderToCommand(req *mgmt_pb.AddSAMLProviderRequest) command.SAMLP
}
}
func updateSAMLProviderToCommand(req *mgmt_pb.UpdateSAMLProviderRequest) command.SAMLProvider {
func updateSAMLProviderToCommand(req *mgmt_pb.UpdateSAMLProviderRequest) *command.SAMLProvider {
var nameIDFormat *domain.SAMLNameIDFormat
if req.NameIdFormat != nil {
nameIDFormat = gu.Ptr(idp_grpc.SAMLNameIDFormatToDomain(req.GetNameIdFormat()))
}
return command.SAMLProvider{
return &command.SAMLProvider{
Name: req.Name,
Metadata: req.GetMetadataXml(),
MetadataURL: req.GetMetadataUrl(),

View File

@ -58,7 +58,7 @@ func TestServer_AddOTPSMS(t *testing.T) {
wantErr: true,
},
{
name: "user mismatch",
name: "no permission",
args: args{
ctx: integration.WithAuthorizationToken(context.Background(), sessionTokenOtherUser),
req: &user.AddOTPSMSRequest{
@ -127,14 +127,24 @@ func TestServer_RemoveOTPSMS(t *testing.T) {
userVerified := Instance.CreateHumanUser(CTX)
Instance.RegisterUserPasskey(CTX, userVerified.GetUserId())
_, sessionTokenVerified, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userVerified.GetUserId())
userVerifiedCtx := integration.WithAuthorizationToken(context.Background(), sessionTokenVerified)
_, err := Instance.Client.UserV2.VerifyPhone(userVerifiedCtx, &user.VerifyPhoneRequest{
_, err := Instance.Client.UserV2.VerifyPhone(CTX, &user.VerifyPhoneRequest{
UserId: userVerified.GetUserId(),
VerificationCode: userVerified.GetPhoneCode(),
})
require.NoError(t, err)
_, err = Instance.Client.UserV2.AddOTPSMS(userVerifiedCtx, &user.AddOTPSMSRequest{UserId: userVerified.GetUserId()})
_, err = Instance.Client.UserV2.AddOTPSMS(CTX, &user.AddOTPSMSRequest{UserId: userVerified.GetUserId()})
require.NoError(t, err)
userSelf := Instance.CreateHumanUser(CTX)
Instance.RegisterUserPasskey(CTX, userSelf.GetUserId())
_, sessionTokenSelf, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userSelf.GetUserId())
userSelfCtx := integration.WithAuthorizationToken(context.Background(), sessionTokenSelf)
_, err = Instance.Client.UserV2.VerifyPhone(CTX, &user.VerifyPhoneRequest{
UserId: userSelf.GetUserId(),
VerificationCode: userSelf.GetPhoneCode(),
})
require.NoError(t, err)
_, err = Instance.Client.UserV2.AddOTPSMS(CTX, &user.AddOTPSMSRequest{UserId: userSelf.GetUserId()})
require.NoError(t, err)
type args struct {
@ -157,10 +167,24 @@ func TestServer_RemoveOTPSMS(t *testing.T) {
},
wantErr: true,
},
{
name: "success, self",
args: args{
ctx: userSelfCtx,
req: &user.RemoveOTPSMSRequest{
UserId: userSelf.GetUserId(),
},
},
want: &user.RemoveOTPSMSResponse{
Details: &object.Details{
ResourceOwner: Instance.DefaultOrg.Details.ResourceOwner,
},
},
},
{
name: "success",
args: args{
ctx: userVerifiedCtx,
ctx: CTX,
req: &user.RemoveOTPSMSRequest{
UserId: userVerified.GetUserId(),
},
@ -230,7 +254,7 @@ func TestServer_AddOTPEmail(t *testing.T) {
wantErr: true,
},
{
name: "user mismatch",
name: "no permission",
args: args{
ctx: integration.WithAuthorizationToken(context.Background(), sessionTokenOtherUser),
req: &user.AddOTPEmailRequest{
@ -301,14 +325,24 @@ func TestServer_RemoveOTPEmail(t *testing.T) {
userVerified := Instance.CreateHumanUser(CTX)
Instance.RegisterUserPasskey(CTX, userVerified.GetUserId())
_, sessionTokenVerified, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userVerified.GetUserId())
userVerifiedCtx := integration.WithAuthorizationToken(context.Background(), sessionTokenVerified)
_, err := Instance.Client.UserV2.VerifyEmail(userVerifiedCtx, &user.VerifyEmailRequest{
_, err := Instance.Client.UserV2.VerifyEmail(CTX, &user.VerifyEmailRequest{
UserId: userVerified.GetUserId(),
VerificationCode: userVerified.GetEmailCode(),
})
require.NoError(t, err)
_, err = Instance.Client.UserV2.AddOTPEmail(userVerifiedCtx, &user.AddOTPEmailRequest{UserId: userVerified.GetUserId()})
_, err = Instance.Client.UserV2.AddOTPEmail(CTX, &user.AddOTPEmailRequest{UserId: userVerified.GetUserId()})
require.NoError(t, err)
userSelf := Instance.CreateHumanUser(CTX)
Instance.RegisterUserPasskey(CTX, userSelf.GetUserId())
_, sessionTokenSelf, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userSelf.GetUserId())
userSelfCtx := integration.WithAuthorizationToken(context.Background(), sessionTokenSelf)
_, err = Instance.Client.UserV2.VerifyEmail(CTX, &user.VerifyEmailRequest{
UserId: userSelf.GetUserId(),
VerificationCode: userSelf.GetEmailCode(),
})
require.NoError(t, err)
_, err = Instance.Client.UserV2.AddOTPEmail(CTX, &user.AddOTPEmailRequest{UserId: userSelf.GetUserId()})
require.NoError(t, err)
type args struct {
@ -331,10 +365,25 @@ func TestServer_RemoveOTPEmail(t *testing.T) {
},
wantErr: true,
},
{
name: "success, self",
args: args{
ctx: userSelfCtx,
req: &user.RemoveOTPEmailRequest{
UserId: userSelf.GetUserId(),
},
},
want: &user.RemoveOTPEmailResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Instance.DefaultOrg.Details.ResourceOwner,
},
},
},
{
name: "success",
args: args{
ctx: userVerifiedCtx,
ctx: CTX,
req: &user.RemoveOTPEmailRequest{
UserId: userVerified.GetUserId(),
},

View File

@ -93,15 +93,30 @@ func TestServer_RegisterPasskey(t *testing.T) {
wantErr: true,
},
{
name: "user mismatch",
name: "user no permission",
args: args{
ctx: CTX,
ctx: UserCTX,
req: &user.RegisterPasskeyRequest{
UserId: userID,
},
},
wantErr: true,
},
{
name: "user permission",
args: args{
ctx: IamCTX,
req: &user.RegisterPasskeyRequest{
UserId: userID,
},
},
want: &user.RegisterPasskeyResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Instance.DefaultOrg.Id,
},
},
},
{
name: "user setting its own passkey",
args: args{

View File

@ -13,7 +13,6 @@ func (s *Server) AddOTPSMS(ctx context.Context, req *user.AddOTPSMSRequest) (*us
return nil, err
}
return &user.AddOTPSMSResponse{Details: object.DomainToDetailsPb(details)}, nil
}
func (s *Server) RemoveOTPSMS(ctx context.Context, req *user.RemoveOTPSMSRequest) (*user.RemoveOTPSMSResponse, error) {

View File

@ -58,7 +58,7 @@ func TestServer_AddOTPSMS(t *testing.T) {
wantErr: true,
},
{
name: "user mismatch",
name: "no permission",
args: args{
ctx: integration.WithAuthorizationToken(context.Background(), sessionTokenOtherUser),
req: &user.AddOTPSMSRequest{
@ -127,14 +127,24 @@ func TestServer_RemoveOTPSMS(t *testing.T) {
userVerified := Instance.CreateHumanUser(CTX)
Instance.RegisterUserPasskey(CTX, userVerified.GetUserId())
_, sessionTokenVerified, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userVerified.GetUserId())
userVerifiedCtx := integration.WithAuthorizationToken(context.Background(), sessionTokenVerified)
_, err := Client.VerifyPhone(userVerifiedCtx, &user.VerifyPhoneRequest{
_, err := Instance.Client.UserV2beta.VerifyPhone(CTX, &user.VerifyPhoneRequest{
UserId: userVerified.GetUserId(),
VerificationCode: userVerified.GetPhoneCode(),
})
require.NoError(t, err)
_, err = Client.AddOTPSMS(userVerifiedCtx, &user.AddOTPSMSRequest{UserId: userVerified.GetUserId()})
_, err = Instance.Client.UserV2beta.AddOTPSMS(CTX, &user.AddOTPSMSRequest{UserId: userVerified.GetUserId()})
require.NoError(t, err)
userSelf := Instance.CreateHumanUser(CTX)
Instance.RegisterUserPasskey(CTX, userSelf.GetUserId())
_, sessionTokenSelf, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userSelf.GetUserId())
userSelfCtx := integration.WithAuthorizationToken(context.Background(), sessionTokenSelf)
_, err = Instance.Client.UserV2beta.VerifyPhone(CTX, &user.VerifyPhoneRequest{
UserId: userSelf.GetUserId(),
VerificationCode: userSelf.GetPhoneCode(),
})
require.NoError(t, err)
_, err = Instance.Client.UserV2beta.AddOTPSMS(CTX, &user.AddOTPSMSRequest{UserId: userSelf.GetUserId()})
require.NoError(t, err)
type args struct {
@ -157,10 +167,24 @@ func TestServer_RemoveOTPSMS(t *testing.T) {
},
wantErr: true,
},
{
name: "success, self",
args: args{
ctx: userSelfCtx,
req: &user.RemoveOTPSMSRequest{
UserId: userSelf.GetUserId(),
},
},
want: &user.RemoveOTPSMSResponse{
Details: &object.Details{
ResourceOwner: Instance.DefaultOrg.Details.ResourceOwner,
},
},
},
{
name: "success",
args: args{
ctx: userVerifiedCtx,
ctx: CTX,
req: &user.RemoveOTPSMSRequest{
UserId: userVerified.GetUserId(),
},
@ -301,14 +325,24 @@ func TestServer_RemoveOTPEmail(t *testing.T) {
userVerified := Instance.CreateHumanUser(CTX)
Instance.RegisterUserPasskey(CTX, userVerified.GetUserId())
_, sessionTokenVerified, _, _ := Instance.CreateVerifiedWebAuthNSession(t, CTX, userVerified.GetUserId())
userVerifiedCtx := integration.WithAuthorizationToken(context.Background(), sessionTokenVerified)
_, err := Client.VerifyEmail(userVerifiedCtx, &user.VerifyEmailRequest{
_, err := Client.VerifyEmail(CTX, &user.VerifyEmailRequest{
UserId: userVerified.GetUserId(),
VerificationCode: userVerified.GetEmailCode(),
})
require.NoError(t, err)
_, err = Client.AddOTPEmail(userVerifiedCtx, &user.AddOTPEmailRequest{UserId: userVerified.GetUserId()})
_, err = Client.AddOTPEmail(CTX, &user.AddOTPEmailRequest{UserId: userVerified.GetUserId()})
require.NoError(t, err)
userSelf := Instance.CreateHumanUser(CTX)
Instance.RegisterUserPasskey(CTX, userSelf.GetUserId())
_, sessionTokenSelf, _, _ := Instance.CreateVerifiedWebAuthNSession(t, IamCTX, userSelf.GetUserId())
userSelfCtx := integration.WithAuthorizationToken(context.Background(), sessionTokenSelf)
_, err = Client.VerifyEmail(CTX, &user.VerifyEmailRequest{
UserId: userSelf.GetUserId(),
VerificationCode: userSelf.GetEmailCode(),
})
require.NoError(t, err)
_, err = Client.AddOTPEmail(CTX, &user.AddOTPEmailRequest{UserId: userSelf.GetUserId()})
require.NoError(t, err)
type args struct {
@ -331,10 +365,25 @@ func TestServer_RemoveOTPEmail(t *testing.T) {
},
wantErr: true,
},
{
name: "success, self",
args: args{
ctx: userSelfCtx,
req: &user.RemoveOTPEmailRequest{
UserId: userSelf.GetUserId(),
},
},
want: &user.RemoveOTPEmailResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Instance.DefaultOrg.Details.ResourceOwner,
},
},
},
{
name: "success",
args: args{
ctx: userVerifiedCtx,
ctx: CTX,
req: &user.RemoveOTPEmailRequest{
UserId: userVerified.GetUserId(),
},

View File

@ -92,15 +92,30 @@ func TestServer_RegisterPasskey(t *testing.T) {
wantErr: true,
},
{
name: "user mismatch",
name: "user no permission",
args: args{
ctx: CTX,
ctx: UserCTX,
req: &user.RegisterPasskeyRequest{
UserId: userID,
},
},
wantErr: true,
},
{
name: "user permission",
args: args{
ctx: IamCTX,
req: &user.RegisterPasskeyRequest{
UserId: userID,
},
},
want: &user.RegisterPasskeyResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Instance.DefaultOrg.Id,
},
},
},
{
name: "user setting its own passkey",
args: args{

View File

@ -11,6 +11,7 @@ import (
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/idp/providers/saml"
"github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/zerrors"
)
@ -511,10 +512,10 @@ func (c *Commands) UpdateInstanceAppleProvider(ctx context.Context, id string, p
return pushedEventsToObjectDetails(pushedEvents), nil
}
func (c *Commands) AddInstanceSAMLProvider(ctx context.Context, provider SAMLProvider) (string, *domain.ObjectDetails, error) {
func (c *Commands) AddInstanceSAMLProvider(ctx context.Context, provider *SAMLProvider) (id string, details *domain.ObjectDetails, err error) {
instanceID := authz.GetInstance(ctx).InstanceID()
instanceAgg := instance.NewAggregate(instanceID)
id, err := c.idGenerator.Next()
id, err = c.idGenerator.Next()
if err != nil {
return "", nil, err
}
@ -530,7 +531,7 @@ func (c *Commands) AddInstanceSAMLProvider(ctx context.Context, provider SAMLPro
return id, pushedEventsToObjectDetails(pushedEvents), nil
}
func (c *Commands) UpdateInstanceSAMLProvider(ctx context.Context, id string, provider SAMLProvider) (*domain.ObjectDetails, error) {
func (c *Commands) UpdateInstanceSAMLProvider(ctx context.Context, id string, provider *SAMLProvider) (details *domain.ObjectDetails, err error) {
instanceID := authz.GetInstance(ctx).InstanceID()
instanceAgg := instance.NewAggregate(instanceID)
writeModel := NewSAMLInstanceIDPWriteModel(instanceID, id)
@ -1719,7 +1720,7 @@ func (c *Commands) prepareUpdateInstanceAppleProvider(a *instance.Aggregate, wri
}
}
func (c *Commands) prepareAddInstanceSAMLProvider(a *instance.Aggregate, writeModel *InstanceSAMLIDPWriteModel, provider SAMLProvider) preparation.Validation {
func (c *Commands) prepareAddInstanceSAMLProvider(a *instance.Aggregate, writeModel *InstanceSAMLIDPWriteModel, provider *SAMLProvider) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "INST-o07zjotgnd", "Errors.Invalid.Argument")
@ -1734,6 +1735,9 @@ func (c *Commands) prepareAddInstanceSAMLProvider(a *instance.Aggregate, writeMo
if len(provider.Metadata) == 0 {
return nil, zerrors.ThrowInvalidArgument(nil, "INST-3bi3esi16t", "Errors.Invalid.Argument")
}
if _, err := saml.ParseMetadata(provider.Metadata); err != nil {
return nil, zerrors.ThrowInvalidArgument(err, "INST-SF3rwhgh", "Errors.Project.App.SAMLMetadataFormat")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
events, err := filter(ctx, writeModel.Query())
if err != nil {
@ -1772,7 +1776,7 @@ func (c *Commands) prepareAddInstanceSAMLProvider(a *instance.Aggregate, writeMo
}
}
func (c *Commands) prepareUpdateInstanceSAMLProvider(a *instance.Aggregate, writeModel *InstanceSAMLIDPWriteModel, provider SAMLProvider) preparation.Validation {
func (c *Commands) prepareUpdateInstanceSAMLProvider(a *instance.Aggregate, writeModel *InstanceSAMLIDPWriteModel, provider *SAMLProvider) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if writeModel.ID = strings.TrimSpace(writeModel.ID); writeModel.ID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "INST-7o3rq1owpm", "Errors.Invalid.Argument")
@ -1790,6 +1794,9 @@ func (c *Commands) prepareUpdateInstanceSAMLProvider(a *instance.Aggregate, writ
}
provider.Metadata = data
}
if _, err := saml.ParseMetadata(provider.Metadata); err != nil {
return nil, zerrors.ThrowInvalidArgument(err, "INST-dsfj3kl2", "Errors.Project.App.SAMLMetadataFormat")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
events, err := filter(ctx, writeModel.Query())
if err != nil {

View File

@ -22,6 +22,73 @@ import (
"github.com/zitadel/zitadel/internal/zerrors"
)
var (
validSAMLMetadata = []byte(`<?xml version="1.0" encoding="UTF-8"?>
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" entityID="http://localhost:8080/saml/v2/metadata" ID="_8b02ecf6-aea4-4eda-96c6-190551f05b07">
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<CanonicalizationMethod xmlns="http://www.w3.org/2000/09/xmldsig#" Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></CanonicalizationMethod>
<SignatureMethod xmlns="http://www.w3.org/2000/09/xmldsig#" Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"></SignatureMethod>
<Reference xmlns="http://www.w3.org/2000/09/xmldsig#" URI="#_8b02ecf6-aea4-4eda-96c6-190551f05b07">
<Transforms xmlns="http://www.w3.org/2000/09/xmldsig#">
<Transform xmlns="http://www.w3.org/2000/09/xmldsig#" Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"></Transform>
<Transform xmlns="http://www.w3.org/2000/09/xmldsig#" Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></Transform>
</Transforms>
<DigestMethod xmlns="http://www.w3.org/2000/09/xmldsig#" Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
<DigestValue xmlns="http://www.w3.org/2000/09/xmldsig#">Tyw4csdpNNq0E7wi5FXWdVNkdPNg+cM6kK21VB2+iF0=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue xmlns="http://www.w3.org/2000/09/xmldsig#">hWQSYmnBJENy/okk2qRDuHaZiyqpDsdV6BF9/T/LNjUh/8z4dV2NEZvkNhFEyQ+bqdj+NmRWvKqpg1dtgNJxQc32+IsLQvXNYyhMCtyG570/jaTOtm8daV4NKJyTV7SdwM6yfXgubz5YCRTyV13W2gBIFYppIRImIv5NDcjz+lEmWhnrkw8G2wRSFUY7VvkDn9rgsTzw/Pnsw6hlzpjGDYPMPx3ux3kjFVevdhFGNo+VC7t9ozruuGyH3yue9Re6FZoqa4oyWaPSOwei0ZH6UNqkX93Eo5Y49QKwaO8Rm+kWsOhdTqebVmCc+SpWbbrZbQj4nSLgWGlvCkZSivmH7ezr4Ol1ZkRetQ92UQ7xJS7E0y6uXAGvdgpDnyqHCOFfhTS6yqltHtc3m7JZex327xkv6e69uAEOSiv++sifVUIE0h/5u3hZLvwmTPrkoRVY4wgZ4ieb86QPvhw4UPeYapOhCBk5RfjoEFIeYwPUw5rtOlpTyeBJiKMpH1+mDAoa+8HQytZoMrnnY1s612vINtY7jU5igMwIk6MitQpRGibnBVBHRc2A6aE+XS333ganFK9hX6TzNkpHUb66NINDZ8Rgb1thn3MABArGlomtM5/enrAixWExZp70TSElor7SBdBW57H7OZCYUCobZuPRDLsCO6LLKeVrbdygWeRqr/o=</SignatureValue>
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<X509Data xmlns="http://www.w3.org/2000/09/xmldsig#">
<X509Certificate xmlns="http://www.w3.org/2000/09/xmldsig#">MIIFIjCCAwqgAwIBAgICA7YwDQYJKoZIhvcNAQELBQAwLDEQMA4GA1UEChMHWklUQURFTDEYMBYGA1UEAxMPWklUQURFTCBTQU1MIENBMB4XDTI0MTEyNzEwMjc0NFoXDTI1MTEyNzE2Mjc0NFowMjEQMA4GA1UEChMHWklUQURFTDEeMBwGA1UEAxMVWklUQURFTCBTQU1MIG1ldGFkYXRhMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEApEpYT7EjbRBp0Hw7PGCiSgUoJtwd2nwZOhGy5WZVWvraAtHzW5ih2B6UwEShjwCmRJZeKYEN9JKJbpAy2EdL/l2rm/pArVNvSQu6sN4izz5p2rd9NfHAO3/EcvYdrelWLQj8WQx6LVM282Z4wbclp8Jz1y8Ow43352hGfFVc1x8gauoNl5MAy4kdbvs8UqihqcRmEyIOWl6UwTApb+XIRSRz0Yop99Fv9ALJwfUppsx+d4j9rlRDvrQJMJz7GC/19L9INTbY0HsVEiTltdAWHwREwrpwxNJQt42p3W/zpf1mjwXd3qNNDZAr1t2POPP4SXd598kabBZ3EMWGGxFw+NYYajyjG5EFOZw09FFJn2jIcovejvigfdqem5DGPECvHefqcqHkBPGukI3RaotXpAYyAGfnV7slVytSW484IX3KloAJLICbETbFGGsGQzIDw8rUqWyaOCOttw2fVNDyRFUMHrGe1PhJ9qA1If+KCWYD0iJqF03rIEhdrvNSdQNYkRa0DdtpacQLpzQtqsUioODqX0W3uzLceJEXLBbU0ZEk8mWZM/auwMo3ycPNXDVwrb6AkUKar+sqSumUuixw7da3KF1/mynh6M2Eo4NRB16oUiyN0EYrit/RRJjsTdH+71cj0V+8KqO88cBpmm+lO6x4RM5xpOf/EwwQHivxgRkCAwEAAaNIMEYwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMCMB8GA1UdIwQYMBaAFIzl7uckcPWldirXeOFL3rH6K8FLMA0GCSqGSIb3DQEBCwUAA4ICAQBz+7R99uX1Us9T4BB2RK3RD9K8Q5foNmxJ8GbxpOQFL8IG1DE3FqBssciJkOsKY+1+Y6eow2TgmD9MxfCY444C8k8YDDjxIcs+4dEaWMUxA6NoEy378ciy0U1E6rpYLxWYTxXmsELyODWwTrRNIiWfbBD2m0w9HYbK6QvX6IYQqYoTOJJ3WJKsMCeQ8XhQsJYNINZEq8RsERY/aikOlTWN7ax4Mkr3bfnz1euXGClExCOM6ej4m2I33i4nyYBvvRkRRZRQCfkAQ+5WFVZoVXrQHNe/Oifit7tfLaDuybcjgkzzY3o0YbczzbdV69fVoj53VpR3QQOB+PCF/VJPUMtUFPEC05yH76g24KVBiM/Ws8GaERW1AxgupHSmvTY3GSiwDXQ2NzgDxUHfRHo8rxenJdEcPlGM0DstbUONDSFGLwvGDiidUVtqj1UB4yGL26bgtmwf61G4qsTn9PJMWdRmCeeOf7fmloRxTA0EEey3bulBBHim466tWHUhgOP+g1X0iE7CnwL8aJ//CCiQOAv1O6x5RLyxrmVTehPLr1T8qvnBmxpmuYU0kfbYpO3tMVe7VLabBx0cYh7izClZKHhgEj1w4aE9tIk7nqVAwvVocT3io8RrcKixlnBrFd7RYIuF3+RsYC/kYEgnZYKAig5u2TySgGmJ7nIS24FYW68WDg==</X509Certificate>
</X509Data>
</KeyInfo>
</Signature>
<IDPSSODescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" WantAuthnRequestsSigned="1" ID="_fd70402c-8a31-4a9a-a4a7-da526524c609" validUntil="2024-12-02T16:54:55.656Z" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<SingleSignOnService xmlns="urn:oasis:names:tc:SAML:2.0:metadata" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost:8080/saml/v2/SSO"></SingleSignOnService>
<SingleSignOnService xmlns="urn:oasis:names:tc:SAML:2.0:metadata" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://localhost:8080/saml/v2/SSO"></SingleSignOnService>
<AttributeProfile>urn:oasis:names:tc:SAML:2.0:profiles:attribute:basic</AttributeProfile>
<Attribute xmlns="urn:oasis:names:tc:SAML:2.0:assertion" Name="Email" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><AttributeValue></AttributeValue></Attribute>
<Attribute xmlns="urn:oasis:names:tc:SAML:2.0:assertion" Name="SurName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><AttributeValue></AttributeValue></Attribute>
<Attribute xmlns="urn:oasis:names:tc:SAML:2.0:assertion" Name="FirstName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><AttributeValue></AttributeValue></Attribute>
<Attribute xmlns="urn:oasis:names:tc:SAML:2.0:assertion" Name="FullName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><AttributeValue></AttributeValue></Attribute>
<Attribute xmlns="urn:oasis:names:tc:SAML:2.0:assertion" Name="UserName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><AttributeValue></AttributeValue></Attribute>
<Attribute xmlns="urn:oasis:names:tc:SAML:2.0:assertion" Name="UserID" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><AttributeValue></AttributeValue></Attribute>
<SingleLogoutService xmlns="urn:oasis:names:tc:SAML:2.0:metadata" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost:8080/saml/v2/SLO"></SingleLogoutService>
<SingleLogoutService xmlns="urn:oasis:names:tc:SAML:2.0:metadata" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://localhost:8080/saml/v2/SLO"></SingleLogoutService>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>
<KeyDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" use="signing">
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<KeyName>http://localhost:8080/saml/v2/metadata IDP signing</KeyName>
<X509Data xmlns="http://www.w3.org/2000/09/xmldsig#">
<X509Certificate xmlns="http://www.w3.org/2000/09/xmldsig#">MIIFIjCCAwqgAwIBAgICA7QwDQYJKoZIhvcNAQELBQAwLDEQMA4GA1UEChMHWklUQURFTDEYMBYGA1UEAxMPWklUQURFTCBTQU1MIENBMB4XDTI0MTEyNzEwMjUwMloXDTI1MTEyNzE2MjUwMlowMjEQMA4GA1UEChMHWklUQURFTDEeMBwGA1UEAxMVWklUQURFTCBTQU1MIHJlc3BvbnNlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA2lUgaI6AS/9xvM9DNSWK6Ho64LpK8UIioM26QfvAfeQ/I2pgX6SwWxEbd7qv+PkJzaFTjrXSlwOmWsJYma+UsdyFClaGFRyCgY8SWxPceandC8a+hQIDS/irLd9XF33RWp0b/09HjQl+n0HZ4teUFDUd2U1mUf3XCpn0+Ho316bmi6xSW6zaMy5RsbUl01hgWj2fgapAsGAHSBphwCE3Dz/9I/UfHWQw1k2/UTgjc9uIujcza6WgOxfsKluXYIOxwNKTfmzzOJMUwXz6GRgB2jhQI29MuKOZOITA7pXq5kZKf0lSRU8zKFTMJaK4zAHQ6f877Drr8XdAHemuXGZ2JdH/Dbdwarzy3YBMCWsAYlpeEvaVAdiSpyR7fAZktNuHd39Zg00Vlj2wdc44Vk5yVssW7pv5qnVZ7JTrXX2uBYFecLAXmplQ2ph1VdSXZLEDGgjiNA2T/fBj7G4/VjsuCBZFm1I0KCJp3HWEJx5dwwhSVc5wOJEzl7fMuPYMKWH/RM6P/7LnO1ulpdmiKPa4gHzdg3hDZn42NKcVt3UYf0phtxpWMrZp/DUEeizhckrC4ed6cfGtS3CUtJEqoycrCROJ5Hy+ONHl5Aqxt+JoPU+t/XATuctfPxQVcDr0itHzo2cjh/AVTU+IC7C0oQHSS9CC8Fp58UqbtYwFtSAd7ecCAwEAAaNIMEYwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMCMB8GA1UdIwQYMBaAFIzl7uckcPWldirXeOFL3rH6K8FLMA0GCSqGSIb3DQEBCwUAA4ICAQAp+IGZScVIbRdCq5HPjlYBPOY7UbL8ZXnlMW/HLELV9GndnULuFhnuQTIdA5dquCsk8RI1fKsScEV1rqWvHZeSo5nVbvUaPJctoD/4GACqE6F8axs1AgSOvpJMyuycjSzSh6gDM1z37Fdqc/2IRqgi7SKdDsfJpi8XW8LtErpp4kyE1rEXopsXG2fe1UH25bZpXraUqYvp61rwVUCazAtV/U7ARG5AnT0mPqzUriIPrfL+v/+2ntV/BSc8/uCqYnHbwpIwjPURCaxo1Pmm6EEkm+V/Ss4ieNwwkD2bLLLST1LoVMim7Ebfy53PEKpsznKsGlVSu0YYKUsStWQVpwhKQw0bQLCJHdpvZtZSDgS9RbSMZz+aY/fpoNx6wDvmMgtdrb3pVXZ8vPKdq9YDrGfFqP60QdZ3CuSHXCM/zX4742GgImJ4KYAcTuF1+BkGf5JLAJOUZBkfCQ/kBT5wr8+EotLxASOC6717whLBYMEG6N8osEk+LDqoJRTLqkzirJsyOHWChKK47yGkdS3HBIZfo91QrJwKpfATYziBjEnqipkTu+6jFylBIkxKTPye4b3vgcodZP8LSNVXAsMGTPNPJxzPWQ37ba4zMnYZ5iUerlaox/SNsn68DT6RajIb1A1JDq+HNFc3hQP2bzk2y5pCax8zo5swjdklnm4clfB2Lw==</X509Certificate>
</X509Data>
</KeyInfo>
</KeyDescriptor>
</IDPSSODescriptor>
<AttributeAuthorityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" ID="_b3fed381-af56-4160-abf5-5ffd1e21cf61" validUntil="2024-12-02T16:54:55.656Z" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<AttributeService xmlns="urn:oasis:names:tc:SAML:2.0:metadata" Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="http://localhost:8080/saml/v2/attribute"></AttributeService>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>
<AttributeProfile>urn:oasis:names:tc:SAML:2.0:profiles:attribute:basic</AttributeProfile>
<Attribute xmlns="urn:oasis:names:tc:SAML:2.0:assertion" Name="Email" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><AttributeValue></AttributeValue></Attribute>
<Attribute xmlns="urn:oasis:names:tc:SAML:2.0:assertion" Name="SurName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><AttributeValue></AttributeValue></Attribute>
<Attribute xmlns="urn:oasis:names:tc:SAML:2.0:assertion" Name="FirstName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><AttributeValue></AttributeValue></Attribute>
<Attribute xmlns="urn:oasis:names:tc:SAML:2.0:assertion" Name="FullName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><AttributeValue></AttributeValue></Attribute>
<Attribute xmlns="urn:oasis:names:tc:SAML:2.0:assertion" Name="UserName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><AttributeValue></AttributeValue></Attribute>
<Attribute xmlns="urn:oasis:names:tc:SAML:2.0:assertion" Name="UserID" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><AttributeValue></AttributeValue></Attribute>
<KeyDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" use="signing">
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<KeyName>http://localhost:8080/saml/v2/metadata IDP signing</KeyName>
<X509Data xmlns="http://www.w3.org/2000/09/xmldsig#">
<X509Certificate xmlns="http://www.w3.org/2000/09/xmldsig#">MIIFIjCCAwqgAwIBAgICA7QwDQYJKoZIhvcNAQELBQAwLDEQMA4GA1UEChMHWklUQURFTDEYMBYGA1UEAxMPWklUQURFTCBTQU1MIENBMB4XDTI0MTEyNzEwMjUwMloXDTI1MTEyNzE2MjUwMlowMjEQMA4GA1UEChMHWklUQURFTDEeMBwGA1UEAxMVWklUQURFTCBTQU1MIHJlc3BvbnNlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA2lUgaI6AS/9xvM9DNSWK6Ho64LpK8UIioM26QfvAfeQ/I2pgX6SwWxEbd7qv+PkJzaFTjrXSlwOmWsJYma+UsdyFClaGFRyCgY8SWxPceandC8a+hQIDS/irLd9XF33RWp0b/09HjQl+n0HZ4teUFDUd2U1mUf3XCpn0+Ho316bmi6xSW6zaMy5RsbUl01hgWj2fgapAsGAHSBphwCE3Dz/9I/UfHWQw1k2/UTgjc9uIujcza6WgOxfsKluXYIOxwNKTfmzzOJMUwXz6GRgB2jhQI29MuKOZOITA7pXq5kZKf0lSRU8zKFTMJaK4zAHQ6f877Drr8XdAHemuXGZ2JdH/Dbdwarzy3YBMCWsAYlpeEvaVAdiSpyR7fAZktNuHd39Zg00Vlj2wdc44Vk5yVssW7pv5qnVZ7JTrXX2uBYFecLAXmplQ2ph1VdSXZLEDGgjiNA2T/fBj7G4/VjsuCBZFm1I0KCJp3HWEJx5dwwhSVc5wOJEzl7fMuPYMKWH/RM6P/7LnO1ulpdmiKPa4gHzdg3hDZn42NKcVt3UYf0phtxpWMrZp/DUEeizhckrC4ed6cfGtS3CUtJEqoycrCROJ5Hy+ONHl5Aqxt+JoPU+t/XATuctfPxQVcDr0itHzo2cjh/AVTU+IC7C0oQHSS9CC8Fp58UqbtYwFtSAd7ecCAwEAAaNIMEYwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMCMB8GA1UdIwQYMBaAFIzl7uckcPWldirXeOFL3rH6K8FLMA0GCSqGSIb3DQEBCwUAA4ICAQAp+IGZScVIbRdCq5HPjlYBPOY7UbL8ZXnlMW/HLELV9GndnULuFhnuQTIdA5dquCsk8RI1fKsScEV1rqWvHZeSo5nVbvUaPJctoD/4GACqE6F8axs1AgSOvpJMyuycjSzSh6gDM1z37Fdqc/2IRqgi7SKdDsfJpi8XW8LtErpp4kyE1rEXopsXG2fe1UH25bZpXraUqYvp61rwVUCazAtV/U7ARG5AnT0mPqzUriIPrfL+v/+2ntV/BSc8/uCqYnHbwpIwjPURCaxo1Pmm6EEkm+V/Ss4ieNwwkD2bLLLST1LoVMim7Ebfy53PEKpsznKsGlVSu0YYKUsStWQVpwhKQw0bQLCJHdpvZtZSDgS9RbSMZz+aY/fpoNx6wDvmMgtdrb3pVXZ8vPKdq9YDrGfFqP60QdZ3CuSHXCM/zX4742GgImJ4KYAcTuF1+BkGf5JLAJOUZBkfCQ/kBT5wr8+EotLxASOC6717whLBYMEG6N8osEk+LDqoJRTLqkzirJsyOHWChKK47yGkdS3HBIZfo91QrJwKpfATYziBjEnqipkTu+6jFylBIkxKTPye4b3vgcodZP8LSNVXAsMGTPNPJxzPWQ37ba4zMnYZ5iUerlaox/SNsn68DT6RajIb1A1JDq+HNFc3hQP2bzk2y5pCax8zo5swjdklnm4clfB2Lw==</X509Certificate>
</X509Data>
</KeyInfo>
</KeyDescriptor>
</AttributeAuthorityDescriptor>
</EntityDescriptor>`)
)
func TestCommandSide_AddInstanceGenericOAuthIDP(t *testing.T) {
type fields struct {
eventstore func(*testing.T) *eventstore.Eventstore
@ -5180,7 +5247,7 @@ func TestCommandSide_AddInstanceSAMLIDP(t *testing.T) {
}
type args struct {
ctx context.Context
provider SAMLProvider
provider *SAMLProvider
}
type res struct {
id string
@ -5201,7 +5268,7 @@ func TestCommandSide_AddInstanceSAMLIDP(t *testing.T) {
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: SAMLProvider{},
provider: &SAMLProvider{},
},
res{
err: func(err error) bool {
@ -5210,14 +5277,14 @@ func TestCommandSide_AddInstanceSAMLIDP(t *testing.T) {
},
},
{
"invalid metadata",
"no metadata",
fields{
eventstore: expectEventstore(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: SAMLProvider{
provider: &SAMLProvider{
Name: "name",
},
},
@ -5227,6 +5294,25 @@ func TestCommandSide_AddInstanceSAMLIDP(t *testing.T) {
},
},
},
{
"invalid metadata, error",
fields{
eventstore: expectEventstore(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: &SAMLProvider{
Name: "name",
Metadata: []byte("metadata"),
},
},
res{
err: func(err error) bool {
return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "INST-SF3rwhgh", "Errors.Project.App.SAMLMetadataFormat"))
},
},
},
{
name: "ok",
fields: fields{
@ -5236,7 +5322,7 @@ func TestCommandSide_AddInstanceSAMLIDP(t *testing.T) {
instance.NewSAMLIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
"id1",
"name",
[]byte("metadata"),
validSAMLMetadata,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
@ -5258,9 +5344,9 @@ func TestCommandSide_AddInstanceSAMLIDP(t *testing.T) {
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: SAMLProvider{
provider: &SAMLProvider{
Name: "name",
Metadata: []byte("metadata"),
Metadata: validSAMLMetadata,
},
},
res: res{
@ -5277,7 +5363,7 @@ func TestCommandSide_AddInstanceSAMLIDP(t *testing.T) {
instance.NewSAMLIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
"id1",
"name",
[]byte("metadata"),
validSAMLMetadata,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
@ -5304,9 +5390,9 @@ func TestCommandSide_AddInstanceSAMLIDP(t *testing.T) {
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: SAMLProvider{
provider: &SAMLProvider{
Name: "name",
Metadata: []byte("metadata"),
Metadata: validSAMLMetadata,
Binding: "binding",
WithSignedRequest: true,
NameIDFormat: gu.Ptr(domain.SAMLNameIDFormatTransient),
@ -5356,7 +5442,7 @@ func TestCommandSide_UpdateInstanceGenericSAMLIDP(t *testing.T) {
type args struct {
ctx context.Context
id string
provider SAMLProvider
provider *SAMLProvider
}
type res struct {
want *domain.ObjectDetails
@ -5375,7 +5461,7 @@ func TestCommandSide_UpdateInstanceGenericSAMLIDP(t *testing.T) {
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: SAMLProvider{},
provider: &SAMLProvider{},
},
res{
err: func(err error) bool {
@ -5391,7 +5477,7 @@ func TestCommandSide_UpdateInstanceGenericSAMLIDP(t *testing.T) {
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1",
provider: SAMLProvider{},
provider: &SAMLProvider{},
},
res{
err: func(err error) bool {
@ -5400,14 +5486,14 @@ func TestCommandSide_UpdateInstanceGenericSAMLIDP(t *testing.T) {
},
},
{
"invalid metadata",
"no metadata",
fields{
eventstore: expectEventstore(),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1",
provider: SAMLProvider{
provider: &SAMLProvider{
Name: "name",
},
},
@ -5417,6 +5503,25 @@ func TestCommandSide_UpdateInstanceGenericSAMLIDP(t *testing.T) {
},
},
},
{
"invalid metadata, error",
fields{
eventstore: expectEventstore(),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1",
provider: &SAMLProvider{
Name: "name",
Metadata: []byte("metadata"),
},
},
res{
err: func(err error) bool {
return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "INST-dsfj3kl2", "Errors.Project.App.SAMLMetadataFormat"))
},
},
},
{
name: "not found",
fields: fields{
@ -5427,9 +5532,9 @@ func TestCommandSide_UpdateInstanceGenericSAMLIDP(t *testing.T) {
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1",
provider: SAMLProvider{
provider: &SAMLProvider{
Name: "name",
Metadata: []byte("metadata"),
Metadata: validSAMLMetadata,
},
},
res: res{
@ -5445,7 +5550,7 @@ func TestCommandSide_UpdateInstanceGenericSAMLIDP(t *testing.T) {
instance.NewSAMLIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
"id1",
"name",
[]byte("metadata"),
validSAMLMetadata,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
@ -5465,9 +5570,9 @@ func TestCommandSide_UpdateInstanceGenericSAMLIDP(t *testing.T) {
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1",
provider: SAMLProvider{
provider: &SAMLProvider{
Name: "name",
Metadata: []byte("metadata"),
Metadata: validSAMLMetadata,
},
},
res: res{
@ -5505,7 +5610,7 @@ func TestCommandSide_UpdateInstanceGenericSAMLIDP(t *testing.T) {
"id1",
[]idp.SAMLIDPChanges{
idp.ChangeSAMLName("new name"),
idp.ChangeSAMLMetadata([]byte("new metadata")),
idp.ChangeSAMLMetadata(validSAMLMetadata),
idp.ChangeSAMLBinding("new binding"),
idp.ChangeSAMLWithSignedRequest(true),
idp.ChangeSAMLNameIDFormat(gu.Ptr(domain.SAMLNameIDFormatTransient)),
@ -5527,9 +5632,9 @@ func TestCommandSide_UpdateInstanceGenericSAMLIDP(t *testing.T) {
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1",
provider: SAMLProvider{
provider: &SAMLProvider{
Name: "new name",
Metadata: []byte("new metadata"),
Metadata: validSAMLMetadata,
Binding: "new binding",
WithSignedRequest: true,
NameIDFormat: gu.Ptr(domain.SAMLNameIDFormatTransient),

View File

@ -10,6 +10,7 @@ import (
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/idp/providers/saml"
"github.com/zitadel/zitadel/internal/repository/org"
"github.com/zitadel/zitadel/internal/zerrors"
)
@ -446,9 +447,9 @@ func (c *Commands) UpdateOrgLDAPProvider(ctx context.Context, resourceOwner, id
return pushedEventsToObjectDetails(pushedEvents), nil
}
func (c *Commands) AddOrgSAMLProvider(ctx context.Context, resourceOwner string, provider SAMLProvider) (string, *domain.ObjectDetails, error) {
func (c *Commands) AddOrgSAMLProvider(ctx context.Context, resourceOwner string, provider *SAMLProvider) (id string, details *domain.ObjectDetails, err error) {
orgAgg := org.NewAggregate(resourceOwner)
id, err := c.idGenerator.Next()
id, err = c.idGenerator.Next()
if err != nil {
return "", nil, err
}
@ -464,7 +465,7 @@ func (c *Commands) AddOrgSAMLProvider(ctx context.Context, resourceOwner string,
return id, pushedEventsToObjectDetails(pushedEvents), nil
}
func (c *Commands) UpdateOrgSAMLProvider(ctx context.Context, resourceOwner, id string, provider SAMLProvider) (*domain.ObjectDetails, error) {
func (c *Commands) UpdateOrgSAMLProvider(ctx context.Context, resourceOwner, id string, provider *SAMLProvider) (details *domain.ObjectDetails, err error) {
orgAgg := org.NewAggregate(resourceOwner)
writeModel := NewSAMLOrgIDPWriteModel(resourceOwner, id)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareUpdateOrgSAMLProvider(orgAgg, writeModel, provider))
@ -1703,7 +1704,7 @@ func (c *Commands) prepareUpdateOrgAppleProvider(a *org.Aggregate, writeModel *O
}
}
func (c *Commands) prepareAddOrgSAMLProvider(a *org.Aggregate, writeModel *OrgSAMLIDPWriteModel, provider SAMLProvider) preparation.Validation {
func (c *Commands) prepareAddOrgSAMLProvider(a *org.Aggregate, writeModel *OrgSAMLIDPWriteModel, provider *SAMLProvider) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "ORG-957lr0f8u3", "Errors.Invalid.Argument")
@ -1718,6 +1719,9 @@ func (c *Commands) prepareAddOrgSAMLProvider(a *org.Aggregate, writeModel *OrgSA
}
provider.Metadata = data
}
if _, err := saml.ParseMetadata(provider.Metadata); err != nil {
return nil, zerrors.ThrowInvalidArgument(err, "ORG-SF3rwhgh", "Errors.Project.App.SAMLMetadataFormat")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
events, err := filter(ctx, writeModel.Query())
if err != nil {
@ -1755,7 +1759,7 @@ func (c *Commands) prepareAddOrgSAMLProvider(a *org.Aggregate, writeModel *OrgSA
}
}
func (c *Commands) prepareUpdateOrgSAMLProvider(a *org.Aggregate, writeModel *OrgSAMLIDPWriteModel, provider SAMLProvider) preparation.Validation {
func (c *Commands) prepareUpdateOrgSAMLProvider(a *org.Aggregate, writeModel *OrgSAMLIDPWriteModel, provider *SAMLProvider) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if writeModel.ID = strings.TrimSpace(writeModel.ID); writeModel.ID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "ORG-wwdwdlaya0", "Errors.Invalid.Argument")
@ -1773,6 +1777,9 @@ func (c *Commands) prepareUpdateOrgSAMLProvider(a *org.Aggregate, writeModel *Or
if provider.Metadata == nil {
return nil, zerrors.ThrowInvalidArgument(nil, "ORG-j6spncd74m", "Errors.Invalid.Argument")
}
if _, err := saml.ParseMetadata(provider.Metadata); err != nil {
return nil, zerrors.ThrowInvalidArgument(err, "ORG-SFqqh42", "Errors.Project.App.SAMLMetadataFormat")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
events, err := filter(ctx, writeModel.Query())
if err != nil {

View File

@ -5348,7 +5348,7 @@ func TestCommandSide_AddOrgSAMLIDP(t *testing.T) {
type args struct {
ctx context.Context
resourceOwner string
provider SAMLProvider
provider *SAMLProvider
}
type res struct {
id string
@ -5370,7 +5370,7 @@ func TestCommandSide_AddOrgSAMLIDP(t *testing.T) {
args{
ctx: context.Background(),
resourceOwner: "org1",
provider: SAMLProvider{},
provider: &SAMLProvider{},
},
res{
err: func(err error) bool {
@ -5379,7 +5379,7 @@ func TestCommandSide_AddOrgSAMLIDP(t *testing.T) {
},
},
{
"invalid metadata",
"no metadata",
fields{
eventstore: expectEventstore(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
@ -5387,7 +5387,7 @@ func TestCommandSide_AddOrgSAMLIDP(t *testing.T) {
args{
ctx: context.Background(),
resourceOwner: "org1",
provider: SAMLProvider{
provider: &SAMLProvider{
Name: "name",
},
},
@ -5397,6 +5397,26 @@ func TestCommandSide_AddOrgSAMLIDP(t *testing.T) {
},
},
},
{
"invalid metadata, fail on error",
fields{
eventstore: expectEventstore(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
},
args{
ctx: context.Background(),
resourceOwner: "org1",
provider: &SAMLProvider{
Name: "name",
Metadata: []byte("metadata"),
},
},
res{
err: func(err error) bool {
return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "ORG-SF3rwhgh", "Errors.Project.App.SAMLMetadataFormat"))
},
},
},
{
name: "ok",
fields: fields{
@ -5406,7 +5426,7 @@ func TestCommandSide_AddOrgSAMLIDP(t *testing.T) {
org.NewSAMLIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
"id1",
"name",
[]byte("metadata"),
validSAMLMetadata,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
@ -5428,9 +5448,9 @@ func TestCommandSide_AddOrgSAMLIDP(t *testing.T) {
args: args{
ctx: context.Background(),
resourceOwner: "org1",
provider: SAMLProvider{
provider: &SAMLProvider{
Name: "name",
Metadata: []byte("metadata"),
Metadata: validSAMLMetadata,
},
},
res: res{
@ -5447,7 +5467,7 @@ func TestCommandSide_AddOrgSAMLIDP(t *testing.T) {
org.NewSAMLIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
"id1",
"name",
[]byte("metadata"),
validSAMLMetadata,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
@ -5475,9 +5495,9 @@ func TestCommandSide_AddOrgSAMLIDP(t *testing.T) {
args: args{
ctx: context.Background(),
resourceOwner: "org1",
provider: SAMLProvider{
provider: &SAMLProvider{
Name: "name",
Metadata: []byte("metadata"),
Metadata: validSAMLMetadata,
Binding: "binding",
WithSignedRequest: true,
NameIDFormat: gu.Ptr(domain.SAMLNameIDFormatTransient),
@ -5528,7 +5548,7 @@ func TestCommandSide_UpdateOrgSAMLIDP(t *testing.T) {
ctx context.Context
resourceOwner string
id string
provider SAMLProvider
provider *SAMLProvider
}
type res struct {
want *domain.ObjectDetails
@ -5548,7 +5568,7 @@ func TestCommandSide_UpdateOrgSAMLIDP(t *testing.T) {
args{
ctx: context.Background(),
resourceOwner: "org1",
provider: SAMLProvider{},
provider: &SAMLProvider{},
},
res{
err: func(err error) bool {
@ -5565,7 +5585,7 @@ func TestCommandSide_UpdateOrgSAMLIDP(t *testing.T) {
ctx: context.Background(),
resourceOwner: "org1",
id: "id1",
provider: SAMLProvider{},
provider: &SAMLProvider{},
},
res{
err: func(err error) bool {
@ -5574,7 +5594,7 @@ func TestCommandSide_UpdateOrgSAMLIDP(t *testing.T) {
},
},
{
"invalid metadata",
"no metadata",
fields{
eventstore: expectEventstore(),
},
@ -5582,7 +5602,7 @@ func TestCommandSide_UpdateOrgSAMLIDP(t *testing.T) {
ctx: context.Background(),
resourceOwner: "org1",
id: "id1",
provider: SAMLProvider{
provider: &SAMLProvider{
Name: "name",
},
},
@ -5592,6 +5612,26 @@ func TestCommandSide_UpdateOrgSAMLIDP(t *testing.T) {
},
},
},
{
"invalid metadata, error",
fields{
eventstore: expectEventstore(),
},
args{
ctx: context.Background(),
resourceOwner: "org1",
id: "id1",
provider: &SAMLProvider{
Name: "name",
Metadata: []byte("metadata"),
},
},
res{
err: func(err error) bool {
return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "ORG-SFqqh42", "Errors.Project.App.SAMLMetadataFormat"))
},
},
},
{
name: "not found",
fields: fields{
@ -5603,9 +5643,9 @@ func TestCommandSide_UpdateOrgSAMLIDP(t *testing.T) {
ctx: context.Background(),
resourceOwner: "org1",
id: "id1",
provider: SAMLProvider{
provider: &SAMLProvider{
Name: "name",
Metadata: []byte("metadata"),
Metadata: validSAMLMetadata,
},
},
res: res{
@ -5623,7 +5663,7 @@ func TestCommandSide_UpdateOrgSAMLIDP(t *testing.T) {
org.NewSAMLIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
"id1",
"name",
[]byte("metadata"),
validSAMLMetadata,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
@ -5644,9 +5684,9 @@ func TestCommandSide_UpdateOrgSAMLIDP(t *testing.T) {
ctx: context.Background(),
resourceOwner: "org1",
id: "id1",
provider: SAMLProvider{
provider: &SAMLProvider{
Name: "name",
Metadata: []byte("metadata"),
Metadata: validSAMLMetadata,
},
},
res: res{
@ -5684,7 +5724,7 @@ func TestCommandSide_UpdateOrgSAMLIDP(t *testing.T) {
"id1",
[]idp.SAMLIDPChanges{
idp.ChangeSAMLName("new name"),
idp.ChangeSAMLMetadata([]byte("new metadata")),
idp.ChangeSAMLMetadata(validSAMLMetadata),
idp.ChangeSAMLBinding("new binding"),
idp.ChangeSAMLWithSignedRequest(true),
idp.ChangeSAMLNameIDFormat(gu.Ptr(domain.SAMLNameIDFormatTransient)),
@ -5707,9 +5747,9 @@ func TestCommandSide_UpdateOrgSAMLIDP(t *testing.T) {
ctx: context.Background(),
resourceOwner: "org1",
id: "id1",
provider: SAMLProvider{
provider: &SAMLProvider{
Name: "new name",
Metadata: []byte("new metadata"),
Metadata: validSAMLMetadata,
Binding: "new binding",
WithSignedRequest: true,
NameIDFormat: gu.Ptr(domain.SAMLNameIDFormatTransient),

View File

@ -205,12 +205,6 @@ func (c *Commands) RemoveProjectGrant(ctx context.Context, projectID, grantID, r
if grantID == "" || projectID == "" {
return details, zerrors.ThrowInvalidArgument(nil, "PROJECT-1m9fJ", "Errors.IDMissing")
}
err = c.checkProjectExists(ctx, projectID, resourceOwner)
if err != nil {
return nil, err
}
existingGrant, err := c.projectGrantWriteModelByID(ctx, grantID, projectID, resourceOwner)
if err != nil {
return details, err

View File

@ -1273,11 +1273,25 @@ func TestCommandSide_RemoveProjectGrant(t *testing.T) {
},
},
{
name: "project not existing, precondition failed error",
name: "project already removed, precondition failed error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectFilter(
eventFromEventPusher(project.NewGrantAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"projectgrant1",
"grantedorg1",
[]string{"key1"},
)),
eventFromEventPusher(
project.NewProjectRemovedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"projectname1",
nil,
),
),
),
),
},
args: args{
@ -1287,7 +1301,7 @@ func TestCommandSide_RemoveProjectGrant(t *testing.T) {
resourceOwner: "org1",
},
res: res{
err: zerrors.IsPreconditionFailed,
err: zerrors.IsNotFound,
},
},
{
@ -1295,15 +1309,6 @@ func TestCommandSide_RemoveProjectGrant(t *testing.T) {
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"projectname1", true, true, true,
domain.PrivateLabelingSettingUnspecified,
),
),
),
expectFilter(),
),
},
@ -1322,15 +1327,6 @@ func TestCommandSide_RemoveProjectGrant(t *testing.T) {
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"projectname1", true, true, true,
domain.PrivateLabelingSettingUnspecified,
),
),
),
expectFilter(
eventFromEventPusher(project.NewGrantAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
@ -1365,15 +1361,6 @@ func TestCommandSide_RemoveProjectGrant(t *testing.T) {
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"projectname1", true, true, true,
domain.PrivateLabelingSettingUnspecified,
),
),
),
expectFilter(
eventFromEventPusher(project.NewGrantAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
@ -1410,15 +1397,6 @@ func TestCommandSide_RemoveProjectGrant(t *testing.T) {
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"projectname1", true, true, true,
domain.PrivateLabelingSettingUnspecified,
),
),
),
expectFilter(
eventFromEventPusher(project.NewGrantAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,

View File

@ -7,7 +7,6 @@ import (
"github.com/pquerna/otp"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/api/authz"
http_util "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/command/preparation"
"github.com/zitadel/zitadel/internal/crypto"
@ -79,10 +78,8 @@ func (c *Commands) createHumanTOTP(ctx context.Context, userID, resourceOwner st
logging.WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get human for loginname")
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-SqyJz", "Errors.User.NotFound")
}
if authz.GetCtxData(ctx).UserID != userID {
if err := c.checkPermission(ctx, domain.PermissionUserCredentialWrite, human.ResourceOwner, userID); err != nil {
return nil, err
}
if err := c.checkPermissionUpdateUserCredentials(ctx, human.ResourceOwner, userID); err != nil {
return nil, err
}
org, err := c.getOrg(ctx, human.ResourceOwner)
if err != nil {
@ -139,10 +136,8 @@ func (c *Commands) HumanCheckMFATOTPSetup(ctx context.Context, userID, code, use
if err != nil {
return nil, err
}
if authz.GetCtxData(ctx).UserID != userID {
if err := c.checkPermission(ctx, domain.PermissionUserCredentialWrite, existingOTP.ResourceOwner, userID); err != nil {
return nil, err
}
if err := c.checkPermissionUpdateUserCredentials(ctx, existingOTP.ResourceOwner, userID); err != nil {
return nil, err
}
if existingOTP.State == domain.MFAStateUnspecified || existingOTP.State == domain.MFAStateRemoved {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-3Mif9s", "Errors.User.MFA.OTP.NotExisting")
@ -242,10 +237,8 @@ func (c *Commands) HumanRemoveTOTP(ctx context.Context, userID, resourceOwner st
if existingOTP.State == domain.MFAStateUnspecified || existingOTP.State == domain.MFAStateRemoved {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-Hd9sd", "Errors.User.MFA.OTP.NotExisting")
}
if userID != authz.GetCtxData(ctx).UserID {
if err := c.checkPermission(ctx, domain.PermissionUserWrite, existingOTP.ResourceOwner, userID); err != nil {
return nil, err
}
if err := c.checkPermissionUpdateUser(ctx, existingOTP.ResourceOwner, userID); err != nil {
return nil, err
}
userAgg := UserAggregateFromWriteModel(&existingOTP.WriteModel)
pushedEvents, err := c.eventstore.Push(ctx, user.NewHumanOTPRemovedEvent(ctx, userAgg))
@ -286,10 +279,8 @@ func (c *Commands) addHumanOTPSMS(ctx context.Context, userID, resourceOwner str
if err != nil {
return nil, err
}
if authz.GetCtxData(ctx).UserID != userID {
if err := c.checkPermission(ctx, domain.PermissionUserCredentialWrite, otpWriteModel.ResourceOwner(), userID); err != nil {
return nil, err
}
if err := c.checkPermissionUpdateUserCredentials(ctx, otpWriteModel.ResourceOwner(), userID); err != nil {
return nil, err
}
if otpWriteModel.otpAdded {
return nil, zerrors.ThrowAlreadyExists(nil, "COMMAND-Ad3g2", "Errors.User.MFA.OTP.AlreadyReady")
@ -318,10 +309,8 @@ func (c *Commands) RemoveHumanOTPSMS(ctx context.Context, userID, resourceOwner
if err != nil {
return nil, err
}
if userID != authz.GetCtxData(ctx).UserID {
if err := c.checkPermission(ctx, domain.PermissionUserWrite, existingOTP.WriteModel.ResourceOwner, userID); err != nil {
return nil, err
}
if err := c.checkPermissionUpdateUser(ctx, existingOTP.WriteModel.ResourceOwner, userID); err != nil {
return nil, err
}
if !existingOTP.otpAdded {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-Sr3h3", "Errors.User.MFA.OTP.NotExisting")
@ -420,10 +409,8 @@ func (c *Commands) addHumanOTPEmail(ctx context.Context, userID, resourceOwner s
if err != nil {
return nil, err
}
if authz.GetCtxData(ctx).UserID != userID {
if err := c.checkPermission(ctx, domain.PermissionUserCredentialWrite, otpWriteModel.ResourceOwner(), userID); err != nil {
return nil, err
}
if err := c.checkPermissionUpdateUserCredentials(ctx, otpWriteModel.ResourceOwner(), userID); err != nil {
return nil, err
}
if otpWriteModel.otpAdded {
return nil, zerrors.ThrowAlreadyExists(nil, "COMMAND-MKL2s", "Errors.User.MFA.OTP.AlreadyReady")
@ -452,10 +439,8 @@ func (c *Commands) RemoveHumanOTPEmail(ctx context.Context, userID, resourceOwne
if err != nil {
return nil, err
}
if userID != authz.GetCtxData(ctx).UserID {
if err := c.checkPermission(ctx, domain.PermissionUserWrite, existingOTP.WriteModel.ResourceOwner, userID); err != nil {
return nil, err
}
if err := c.checkPermissionUpdateUser(ctx, existingOTP.WriteModel.ResourceOwner, userID); err != nil {
return nil, err
}
if !existingOTP.otpAdded {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-b312D", "Errors.User.MFA.OTP.NotExisting")

View File

@ -110,7 +110,7 @@ type setPasswordVerification func(ctx context.Context) (newEncodedPassword strin
// setPasswordWithPermission returns a permission check as [setPasswordVerification] implementation
func (c *Commands) setPasswordWithPermission(userID, orgID string) setPasswordVerification {
return func(ctx context.Context) (_ string, err error) {
return "", c.checkPermission(ctx, domain.PermissionUserWrite, orgID, userID)
return "", c.checkPermissionUpdateUser(ctx, orgID, userID)
}
}

View File

@ -6,7 +6,6 @@ import (
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
@ -146,10 +145,8 @@ func (c *Commands) addHumanWebAuthN(ctx context.Context, userID, resourceowner,
if err != nil {
return nil, nil, nil, err
}
if authz.GetCtxData(ctx).UserID != userID {
if err = c.checkPermission(ctx, domain.PermissionUserCredentialWrite, user.ResourceOwner, userID); err != nil {
return nil, nil, nil, err
}
if err := c.checkPermissionUpdateUserCredentials(ctx, user.ResourceOwner, userID); err != nil {
return nil, nil, nil, err
}
org, err := c.getOrg(ctx, user.ResourceOwner)
if err != nil {
@ -603,10 +600,9 @@ func (c *Commands) removeHumanWebAuthN(ctx context.Context, userID, webAuthNID,
if existingWebAuthN.State == domain.MFAStateUnspecified || existingWebAuthN.State == domain.MFAStateRemoved {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-DAfb2", "Errors.User.WebAuthN.NotFound")
}
if userID != authz.GetCtxData(ctx).UserID {
if err := c.checkPermission(ctx, domain.PermissionUserWrite, existingWebAuthN.ResourceOwner, existingWebAuthN.AggregateID); err != nil {
return nil, err
}
if err := c.checkPermissionUpdateUser(ctx, existingWebAuthN.ResourceOwner, existingWebAuthN.AggregateID); err != nil {
return nil, err
}
userAgg := UserAggregateFromWriteModel(&existingWebAuthN.WriteModel)

View File

@ -127,6 +127,16 @@ func (c *Commands) checkPermissionUpdateUser(ctx context.Context, resourceOwner,
return nil
}
func (c *Commands) checkPermissionUpdateUserCredentials(ctx context.Context, resourceOwner, userID string) error {
if userID != "" && userID == authz.GetCtxData(ctx).UserID {
return nil
}
if err := c.checkPermission(ctx, domain.PermissionUserCredentialWrite, resourceOwner, userID); err != nil {
return err
}
return nil
}
func (c *Commands) checkPermissionDeleteUser(ctx context.Context, resourceOwner, userID string) error {
if userID != "" && userID == authz.GetCtxData(ctx).UserID {
return nil

View File

@ -6,7 +6,6 @@ import (
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
@ -118,10 +117,8 @@ func (c *Commands) changeUserEmailWithGeneratorEvents(ctx context.Context, userI
if err != nil {
return nil, err
}
if authz.GetCtxData(ctx).UserID != userID {
if err = c.checkPermission(ctx, domain.PermissionUserWrite, cmd.aggregate.ResourceOwner, userID); err != nil {
return nil, err
}
if err = c.checkPermissionUpdateUser(ctx, cmd.aggregate.ResourceOwner, userID); err != nil {
return nil, err
}
if err = cmd.Change(ctx, domain.EmailAddress(email)); err != nil {
return nil, err
@ -137,10 +134,8 @@ func (c *Commands) resendUserEmailCodeWithGeneratorEvents(ctx context.Context, u
if err != nil {
return nil, err
}
if authz.GetCtxData(ctx).UserID != userID {
if err = c.checkPermission(ctx, domain.PermissionUserWrite, cmd.aggregate.ResourceOwner, userID); err != nil {
return nil, err
}
if err = c.checkPermissionUpdateUser(ctx, cmd.aggregate.ResourceOwner, userID); err != nil {
return nil, err
}
if cmd.model.Code == nil {
return nil, zerrors.ThrowPreconditionFailed(err, "EMAIL-5w5ilin4yt", "Errors.User.Code.Empty")

View File

@ -6,7 +6,6 @@ import (
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
@ -74,10 +73,8 @@ func (c *Commands) ResendInviteCode(ctx context.Context, userID, resourceOwner,
if err != nil {
return nil, err
}
if authz.GetCtxData(ctx).UserID != userID {
if err := c.checkPermission(ctx, domain.PermissionUserWrite, existingCode.ResourceOwner, userID); err != nil {
return nil, err
}
if err := c.checkPermissionUpdateUser(ctx, existingCode.ResourceOwner, userID); err != nil {
return nil, err
}
if !existingCode.UserState.Exists() {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-H3b2a", "Errors.User.NotFound")

View File

@ -6,7 +6,6 @@ import (
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/command/preparation"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
@ -18,7 +17,7 @@ import (
// RegisterUserPasskey creates a passkey registration for the current authenticated user.
// UserID, usually taken from the request is compared against the user ID in the context.
func (c *Commands) RegisterUserPasskey(ctx context.Context, userID, resourceOwner, rpID string, authenticator domain.AuthenticatorAttachment) (*domain.WebAuthNRegistrationDetails, error) {
if err := authz.UserIDInCTX(ctx, userID); err != nil {
if err := c.checkPermissionUpdateUserCredentials(ctx, resourceOwner, userID); err != nil {
return nil, err
}
return c.registerUserPasskey(ctx, userID, resourceOwner, rpID, authenticator)

View File

@ -34,8 +34,9 @@ func TestCommands_RegisterUserPasskey(t *testing.T) {
}
userAgg := &user.NewAggregate("user1", "org1").Aggregate
type fields struct {
eventstore *eventstore.Eventstore
idGenerator id.Generator
eventstore func(t *testing.T) *eventstore.Eventstore
idGenerator id.Generator
checkPermission domain.PermissionCheck
}
type args struct {
userID string
@ -51,18 +52,22 @@ func TestCommands_RegisterUserPasskey(t *testing.T) {
wantErr error
}{
{
name: "wrong user",
name: "no permission",
fields: fields{
eventstore: expectEventstore(),
checkPermission: newMockPermissionCheckNotAllowed(),
},
args: args{
userID: "foo",
resourceOwner: "org1",
authenticator: domain.AuthenticatorAttachmentCrossPlattform,
},
wantErr: zerrors.ThrowPermissionDenied(nil, "AUTH-Bohd2", "Errors.User.UserIDWrong"),
wantErr: zerrors.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied"),
},
{
name: "get human passwordless error",
fields: fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilterError(io.ErrClosedPipe),
),
},
@ -76,7 +81,7 @@ func TestCommands_RegisterUserPasskey(t *testing.T) {
{
name: "id generator error",
fields: fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilter(), // getHumanPasswordlessTokens
expectFilter(eventFromEventPusher(
user.NewHumanAddedEvent(ctx,
@ -118,9 +123,10 @@ func TestCommands_RegisterUserPasskey(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore,
idGenerator: tt.fields.idGenerator,
webauthnConfig: webauthnConfig,
eventstore: tt.fields.eventstore(t),
idGenerator: tt.fields.idGenerator,
webauthnConfig: webauthnConfig,
checkPermission: tt.fields.checkPermission,
}
_, err := c.RegisterUserPasskey(ctx, tt.args.userID, tt.args.resourceOwner, tt.args.rpID, tt.args.authenticator)
require.ErrorIs(t, err, tt.wantErr)

View File

@ -4,7 +4,6 @@ import (
"context"
"io"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/repository/user"
"github.com/zitadel/zitadel/internal/zerrors"
@ -50,10 +49,8 @@ func (c *Commands) requestPasswordReset(ctx context.Context, userID string, retu
if model.UserState == domain.UserStateInitial {
return nil, nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Sfe4g", "Errors.User.NotInitialised")
}
if authz.GetCtxData(ctx).UserID != userID {
if err = c.checkPermission(ctx, domain.PermissionUserWrite, model.ResourceOwner, userID); err != nil {
return nil, nil, err
}
if err = c.checkPermissionUpdateUser(ctx, model.ResourceOwner, userID); err != nil {
return nil, nil, err
}
var passwordCode *EncryptedCode
var generatorID string

View File

@ -82,10 +82,8 @@ func (c *Commands) changeUserPhoneWithGenerator(ctx context.Context, userID, pho
if err != nil {
return nil, err
}
if authz.GetCtxData(ctx).UserID != userID {
if err = c.checkPermission(ctx, domain.PermissionUserWrite, cmd.aggregate.ResourceOwner, userID); err != nil {
return nil, err
}
if err = c.checkPermissionUpdateUser(ctx, cmd.aggregate.ResourceOwner, userID); err != nil {
return nil, err
}
if err = cmd.Change(ctx, domain.PhoneNumber(phone)); err != nil {
return nil, err
@ -104,10 +102,8 @@ func (c *Commands) resendUserPhoneCodeWithGenerator(ctx context.Context, userID
if err != nil {
return nil, err
}
if authz.GetCtxData(ctx).UserID != userID {
if err = c.checkPermission(ctx, domain.PermissionUserWrite, cmd.aggregate.ResourceOwner, userID); err != nil {
return nil, err
}
if err = c.checkPermissionUpdateUser(ctx, cmd.aggregate.ResourceOwner, userID); err != nil {
return nil, err
}
if cmd.model.Code == nil && cmd.model.GeneratorID == "" {
return nil, zerrors.ThrowPreconditionFailed(err, "PHONE-5xrra88eq8", "Errors.User.Code.Empty")

View File

@ -1,15 +1,19 @@
package saml
import (
"bytes"
"context"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/xml"
"io"
"net/url"
"time"
"github.com/crewjam/saml"
"github.com/crewjam/saml/samlsp"
"golang.org/x/text/encoding/ianaindex"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/idp"
@ -104,6 +108,41 @@ func WithEntityID(entityID string) ProviderOpts {
}
}
// ParseMetadata parses the metadata with the provided XML encoding and returns the EntityDescriptor
func ParseMetadata(metadata []byte) (*saml.EntityDescriptor, error) {
entityDescriptor := new(saml.EntityDescriptor)
reader := bytes.NewReader(metadata)
decoder := xml.NewDecoder(reader)
decoder.CharsetReader = func(charset string, reader io.Reader) (io.Reader, error) {
enc, err := ianaindex.IANA.Encoding(charset)
if err != nil {
return nil, err
}
return enc.NewDecoder().Reader(reader), nil
}
if err := decoder.Decode(entityDescriptor); err != nil {
if err.Error() == "expected element type <EntityDescriptor> but have <EntitiesDescriptor>" {
// reset reader to start of metadata so we can try to parse it as an EntitiesDescriptor
if _, err := reader.Seek(0, io.SeekStart); err != nil {
return nil, err
}
entities := &saml.EntitiesDescriptor{}
if err := decoder.Decode(entities); err != nil {
return nil, err
}
for i, e := range entities.EntityDescriptors {
if len(e.IDPSSODescriptors) > 0 {
return &entities.EntityDescriptors[i], nil
}
}
return nil, zerrors.ThrowInternal(nil, "SAML-Ejoi3r2", "no entity found with IDPSSODescriptor")
}
return nil, err
}
return entityDescriptor, nil
}
func New(
name string,
rootURLStr string,
@ -112,8 +151,8 @@ func New(
key []byte,
options ...ProviderOpts,
) (*Provider, error) {
entityDescriptor := new(saml.EntityDescriptor)
if err := xml.Unmarshal(metadata, entityDescriptor); err != nil {
entityDescriptor, err := ParseMetadata(metadata)
if err != nil {
return nil, err
}
keyPair, err := tls.X509KeyPair(certificate, key)
@ -180,6 +219,7 @@ func (p *Provider) GetSP() (*samlsp.Middleware, error) {
if p.binding != "" {
sp.Binding = p.binding
}
sp.ServiceProvider.MetadataValidDuration = time.Until(sp.ServiceProvider.Certificate.NotAfter)
return sp, nil
}

View File

@ -1,6 +1,7 @@
package saml
import (
"encoding/xml"
"testing"
"github.com/crewjam/saml"
@ -10,6 +11,7 @@ import (
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/idp/providers/saml/requesttracker"
"github.com/zitadel/zitadel/internal/zerrors"
)
func TestProvider_Options(t *testing.T) {
@ -170,3 +172,111 @@ func TestProvider_Options(t *testing.T) {
})
}
}
func TestParseMetadata(t *testing.T) {
type args struct {
metadata []byte
}
tests := []struct {
name string
args args
want *saml.EntityDescriptor
wantErr error
}{
{
"invalid",
args{
metadata: []byte(`<Test></Test>`),
},
nil,
xml.UnmarshalError("expected element type <EntityDescriptor> but have <Test>"),
},
{
"valid entity descriptor",
args{
metadata: []byte(`<?xml version="1.0" encoding="UTF-8"?><EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" entityID="http://localhost:8000/metadata"><IDPSSODescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"><SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost:8000/sso"></SingleSignOnService></IDPSSODescriptor></EntityDescriptor>`),
},
&saml.EntityDescriptor{
EntityID: "http://localhost:8000/metadata",
IDPSSODescriptors: []saml.IDPSSODescriptor{
{
XMLName: xml.Name{
Space: "urn:oasis:names:tc:SAML:2.0:metadata",
Local: "IDPSSODescriptor",
},
SingleSignOnServices: []saml.Endpoint{
{
Binding: saml.HTTPRedirectBinding,
Location: "http://localhost:8000/sso",
},
},
},
},
},
nil,
},
{
"valid entity descriptor, non utf-8",
args{
metadata: []byte(`<?xml version="1.0" encoding="windows-1252"?><EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" entityID="http://localhost:8000/metadata"><IDPSSODescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"><SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost:8000/sso"></SingleSignOnService></IDPSSODescriptor></EntityDescriptor>`),
},
&saml.EntityDescriptor{
EntityID: "http://localhost:8000/metadata",
IDPSSODescriptors: []saml.IDPSSODescriptor{
{
XMLName: xml.Name{
Space: "urn:oasis:names:tc:SAML:2.0:metadata",
Local: "IDPSSODescriptor",
},
SingleSignOnServices: []saml.Endpoint{
{
Binding: saml.HTTPRedirectBinding,
Location: "http://localhost:8000/sso",
},
},
},
},
},
nil,
},
{
"entities descriptor without IDPSSODescriptor",
args{
metadata: []byte(`<?xml version="1.0" encoding="UTF-8"?><EntitiesDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"><EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" entityID="http://localhost:8000/metadata"></EntityDescriptor></EntitiesDescriptor>`),
},
nil,
zerrors.ThrowInternal(nil, "SAML-Ejoi3r2", "no entity found with IDPSSODescriptor"),
},
{
"valid entities descriptor",
args{
metadata: []byte(`<?xml version="1.0" encoding="UTF-8"?><EntitiesDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"><EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" entityID="http://localhost:8000/metadata"><IDPSSODescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"><SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost:8000/sso"></SingleSignOnService></IDPSSODescriptor></EntityDescriptor></EntitiesDescriptor>`),
},
&saml.EntityDescriptor{
EntityID: "http://localhost:8000/metadata",
IDPSSODescriptors: []saml.IDPSSODescriptor{
{
XMLName: xml.Name{
Space: "urn:oasis:names:tc:SAML:2.0:metadata",
Local: "IDPSSODescriptor",
},
SingleSignOnServices: []saml.Endpoint{
{
Binding: saml.HTTPRedirectBinding,
Location: "http://localhost:8000/sso",
},
},
},
},
},
nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseMetadata(tt.args.metadata)
assert.ErrorIs(t, err, tt.wantErr)
assert.Equal(t, tt.want, got)
})
}
}

View File

@ -9,7 +9,6 @@ import (
"github.com/crewjam/saml"
"github.com/crewjam/saml/samlsp"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/idp"
"github.com/zitadel/zitadel/internal/zerrors"
@ -71,7 +70,7 @@ func (s *Session) FetchUser(ctx context.Context) (user idp.User, err error) {
if err != nil {
invalidRespErr := new(saml.InvalidResponseError)
if errors.As(err, &invalidRespErr) {
logging.WithError(invalidRespErr.PrivateErr).Info("invalid SAML response details")
return nil, zerrors.ThrowInvalidArgument(invalidRespErr.PrivateErr, "SAML-ajl3irfs", "Errors.Intent.ResponseInvalid")
}
return nil, zerrors.ThrowInvalidArgument(err, "SAML-nuo0vphhh9", "Errors.Intent.ResponseInvalid")
}

View File

@ -134,7 +134,7 @@ func TestSession_FetchUser(t *testing.T) {
requestID: "id-b22c90db88bf01d82ffb0a7b6fe25ac9fcb2c679",
},
want: want{
err: zerrors.ThrowInvalidArgument(nil, "SAML-nuo0vphhh9", "Errors.Intent.ResponseInvalid"),
err: zerrors.ThrowInvalidArgument(nil, "SAML-ajl3irfs", "Errors.Intent.ResponseInvalid"),
},
},
{