feat: map allowed groups to OIDC clients (#202)

This commit is contained in:
Elias Schneider
2025-02-03 18:41:15 +01:00
committed by GitHub
parent 430421e98b
commit 13b02a072f
30 changed files with 518 additions and 218 deletions

View File

@@ -40,7 +40,7 @@ test('Update account details fails with already taken username', async ({ page }
test('Add passkey to an account', async ({ page }) => {
await page.goto('/settings/account');
await (await passkeyUtil.init(page)).addPasskey('new');
await (await passkeyUtil.init(page)).addPasskey('timNew');
await page.click('button:text("Add Passkey")');

View File

@@ -24,6 +24,8 @@ test('Update general configuration', async ({ page }) => {
test('Update email configuration', async ({ page }) => {
await page.goto('/settings/admin/application-configuration');
await page.getByRole('button', { name: 'Expand card' }).nth(1).click();
await page.getByLabel('SMTP Host').fill('smtp.gmail.com');
await page.getByLabel('SMTP Port').fill('587');
await page.getByLabel('SMTP User').fill('test@gmail.com');
@@ -47,14 +49,53 @@ test('Update email configuration', async ({ page }) => {
await expect(page.getByLabel('Email One Time Access')).toBeChecked();
});
test('Update LDAP configuration', async ({ page }) => {
await page.goto('/settings/admin/application-configuration');
await page.getByRole('button', { name: 'Expand card' }).nth(2).click();
await page.getByLabel('LDAP URL').fill('ldap://localhost:389');
await page.getByLabel('LDAP Bind DN').fill('cn=admin,dc=example,dc=com');
await page.getByLabel('LDAP Bind Password').fill('password');
await page.getByLabel('LDAP Base DN').fill('dc=example,dc=com');
await page.getByLabel('User Unique Identifier Attribute').fill('uuid');
await page.getByLabel('Username Attribute').fill('uid');
await page.getByLabel('User Mail Attribute').fill('mail');
await page.getByLabel('User First Name Attribute').fill('givenName');
await page.getByLabel('User Last Name Attribute').fill('sn');
await page.getByLabel('Group Unique Identifier Attribute').fill('uuid');
await page.getByLabel('Group Name Attribute').fill('cn');
await page.getByLabel('Admin Group Name').fill('admin');
await page.getByRole('button', { name: 'Enable' }).click();
await expect(page.getByRole('status')).toHaveText('LDAP configuration updated successfully');
await page.reload();
await expect(page.getByRole('button', { name: 'Disable' })).toBeVisible();
await expect(page.getByLabel('LDAP URL')).toHaveValue('ldap://localhost:389');
await expect(page.getByLabel('LDAP Bind DN')).toHaveValue('cn=admin,dc=example,dc=com');
await expect(page.getByLabel('LDAP Bind Password')).toHaveValue('password');
await expect(page.getByLabel('LDAP Base DN')).toHaveValue('dc=example,dc=com');
await expect(page.getByLabel('User Unique Identifier Attribute')).toHaveValue('uuid');
await expect(page.getByLabel('Username Attribute')).toHaveValue('uid');
await expect(page.getByLabel('User Mail Attribute')).toHaveValue('mail');
await expect(page.getByLabel('User First Name Attribute')).toHaveValue('givenName');
await expect(page.getByLabel('User Last Name Attribute')).toHaveValue('sn');
await expect(page.getByLabel('Admin Group Name')).toHaveValue('admin');
});
test('Update application images', async ({ page }) => {
await page.goto('/settings/admin/application-configuration');
await page.getByRole('button', { name: 'Expand card' }).nth(3).click();
await page.getByLabel('Favicon').setInputFiles('tests/assets/w3-schools-favicon.ico');
await page.getByLabel('Light Mode Logo').setInputFiles('tests/assets/pingvin-share-logo.png');
await page.getByLabel('Dark Mode Logo').setInputFiles('tests/assets/nextcloud-logo.png');
await page.getByLabel('Background Image').setInputFiles('tests/assets/clouds.jpg');
await page.getByRole('button', { name: 'Save' }).nth(2).click();
await page.getByRole('button', { name: 'Save' }).nth(1).click();
await expect(page.getByRole('status')).toHaveText('Images updated successfully');

View File

@@ -75,6 +75,24 @@ test('Authorize new client while not signed in', async ({ page }) => {
});
});
test('Authorize new client fails with user group not allowed', async ({ page }) => {
const oidcClient = oidcClients.immich;
const urlParams = createUrlParams(oidcClient);
await page.context().clearCookies();
await page.goto(`/authorize?${urlParams.toString()}`);
await (await passkeyUtil.init(page)).addPasskey('craig');
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page.getByTestId('scopes').getByRole('heading', { name: 'Email' })).toBeVisible();
await expect(page.getByTestId('scopes').getByRole('heading', { name: 'Profile' })).toBeVisible();
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page.getByRole('paragraph').first()).toHaveText("You're not allowed to access this service.");
});
function createUrlParams(oidcClient: { id: string; callbackUrl: string }) {
return new URLSearchParams({
client_id: oidcClient.id,

View File

@@ -77,6 +77,8 @@ test('Delete user group', async ({ page }) => {
test('Update user group custom claims', async ({ page }) => {
await page.goto(`/settings/admin/user-groups/${userGroups.designers.id}`);
await page.getByRole('button', { name: 'Expand card' }).click();
// Add two custom claims
await page.getByRole('button', { name: 'Add custom claim' }).click();

View File

@@ -142,6 +142,8 @@ test('Update user fails with already taken username', async ({ page }) => {
test('Update user custom claims', async ({ page }) => {
await page.goto(`/settings/admin/users/${users.craig.id}`);
await page.getByRole('button', { name: 'Expand card' }).click();
// Add two custom claims
await page.getByRole('button', { name: 'Add custom claim' }).click();

View File

@@ -2,18 +2,21 @@ import type { CDPSession, Page } from '@playwright/test';
// The existing passkeys are already stored in the database
const passkeys = {
existing1: {
credentialId: 'test-credential-1',
tim: {
credentialId: 'test-credential-tim',
userHandle: 'f4b89dc2-62fb-46bf-9f5f-c34f4eafe93e',
privateKey:
'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg3rNKkGApsEA1TpGiphKh6axTq3Vh6wBghLLea/YkIp+hRANCAATBw6jkpXXr0pHrtAQetxiR5cTcILG/YGDCdKrhVhNDHIu12YrF6B7Frwl3AUqEpdrYEwj3Fo3XkGgvrBIJEUmG'
},
existing2: {
credentialId: 'test-credential-2',
craig: {
credentialId: 'test-credential-craig',
userHandle: '1cd19686-f9a6-43f4-a41f-14a0bf5b4036',
privateKey:
'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg3rNKkGApsEA1TpGiphKh6axTq3Vh6wBghLLea/YkIp+hRANCAATBw6jkpXXr0pHrtAQetxiR5cTcILG/YGDCdKrhVhNDHIu12YrF6B7Frwl3AUqEpdrYEwj3Fo3XkGgvrBIJEUmG'
'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgL1UaeWG1KYpN+HcxQvXEJysiQjT9Fn7Zif3i5cY+s+yhRANCAASPioDQ+tnODwKjULbufJRvOunwTCOvt46UYjYt+vOZsvmc+FlEB0neERqqscxKckGF8yq1AYrANiloshAUAouH'
},
new: {
credentialId: 'new-test-credential',
timNew: {
credentialId: 'new-test-credential-tim',
userHandle: 'f4b89dc2-62fb-46bf-9f5f-c34f4eafe93e',
privateKey:
'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgFl2lIlRyc2G7O9D8WWrw2N8D7NTlhgWcKFY7jYxrfcmhRANCAASmvbCFrXshUvW7avTIysV9UymbhmUwGb7AonUMQPgqK2Jur7PWp9V0AIe5YMuXYH1oxsqY5CoAbdY2YsPmhYoX'
}
@@ -48,9 +51,9 @@ async function addVirtualAuthenticator(client: CDPSession): Promise<string> {
async function addPasskey(
authenticatorId: string,
client: CDPSession,
passkeyName?: keyof typeof passkeys
passkeyName: keyof typeof passkeys = 'tim'
): Promise<void> {
const passkey = passkeys[passkeyName ?? 'existing1'];
const passkey = passkeys[passkeyName];
await client.send('WebAuthn.addCredential', {
authenticatorId,
credential: {
@@ -58,9 +61,8 @@ async function addPasskey(
isResidentCredential: true,
rpId: 'localhost',
privateKey: passkey.privateKey,
userHandle: btoa('f4b89dc2-62fb-46bf-9f5f-c34f4eafe93e'),
userHandle: btoa(passkey.userHandle),
signCount: Math.round((new Date().getTime() - 1704444610871) / 1000 / 2)
// signCount: 2,
}
});
}