mirror of
https://github.com/photoprism/photoprism.git
synced 2026-07-17 16:49:04 +00:00
Users: Centralize Add Account auth field gating in the user model
This commit is contained in:
parent
41fdb578b2
commit
d2a2597ab6
2 changed files with 135 additions and 2 deletions
|
|
@ -237,6 +237,57 @@ export class User extends RestModel {
|
|||
return !this.AuthProvider || this.AuthProvider === "default" || this.AuthProvider === "local";
|
||||
}
|
||||
|
||||
// showsPasswordField reports whether the local password input is shown.
|
||||
// OIDC may keep a local password as a fallback; LDAP replaces it; "none" disables auth.
|
||||
showsPasswordField() {
|
||||
return ["default", "local", "oidc"].includes(this.AuthProvider);
|
||||
}
|
||||
|
||||
// passwordIsRequired reports whether a local password must be set to create the account.
|
||||
// "default" needs one unless an external identity can be supplied instead (OIDC/LDAP configured).
|
||||
passwordIsRequired() {
|
||||
if (this.AuthProvider === "local") {
|
||||
return true;
|
||||
}
|
||||
if (this.AuthProvider === "default") {
|
||||
return !this.showsAuthIdField();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// showsAuthIdField reports whether the external identity input (OIDC Subject ID or LDAP DN) is shown.
|
||||
// "default" offers it only when OIDC or LDAP is configured, to pre-provision external accounts.
|
||||
showsAuthIdField() {
|
||||
const p = this.AuthProvider;
|
||||
return p === "oidc" || p === "ldap" || (p === "default" && ($config.oidcEnabled() || $config.ldapEnabled()));
|
||||
}
|
||||
|
||||
// authIdIsRequired reports whether the external identity must be set; only OIDC requires it up front.
|
||||
authIdIsRequired() {
|
||||
return this.AuthProvider === "oidc";
|
||||
}
|
||||
|
||||
// authIdIsDn reports whether the external identity is an LDAP DN rather than an OIDC Subject ID.
|
||||
authIdIsDn() {
|
||||
const p = this.AuthProvider;
|
||||
return p === "ldap" || (p === "default" && $config.ldapEnabled() && !$config.oidcEnabled());
|
||||
}
|
||||
|
||||
// authIdFieldLabel returns the label for the external identity input.
|
||||
authIdFieldLabel() {
|
||||
return this.authIdIsDn() ? "Distinguished Name (DN)" : "Subject ID";
|
||||
}
|
||||
|
||||
// hasLoginCredential reports whether enough is set for the new account to authenticate.
|
||||
// Covers the gap per-field rules miss: "default" with external auth configured needs a
|
||||
// password, an LDAP directory (username resolves it), or an OIDC Subject ID — else it is inert.
|
||||
hasLoginCredential() {
|
||||
if (this.AuthProvider !== "default" || !this.showsAuthIdField()) {
|
||||
return true;
|
||||
}
|
||||
return !!this.Password || $config.ldapEnabled() || !!this.AuthID;
|
||||
}
|
||||
|
||||
// canEnableLogin reports whether web/API login can be enabled for this account.
|
||||
// System users, role-less accounts, visitors, and deactivated accounts (provider
|
||||
// "none") cannot log in regardless of the toggle. LDAP and OIDC accounts can.
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { describe, it, expect } from "vitest";
|
||||
import { describe, it, expect, afterEach } from "vitest";
|
||||
import "../fixtures";
|
||||
import User from "model/user";
|
||||
import File from "model/file";
|
||||
import Config from "common/config";
|
||||
import StorageShim from "node-storage-shim";
|
||||
import { $session } from "app/session";
|
||||
import { $config, $session } from "app/session";
|
||||
|
||||
const defaultConfig = new Config(new StorageShim(), window.__CONFIG__);
|
||||
|
||||
|
|
@ -457,4 +457,86 @@ describe("model/user", () => {
|
|||
expect(new User({ ID: 1, Name: "a", Role: "admin" }).isClusterAdmin()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// Account-dialog auth field gating shared by the Pro and Portal "Add Account"
|
||||
// dialogs. The external-auth branches read the global $config (ext.oidc/ext.ldap),
|
||||
// so each case sets and restores it explicitly.
|
||||
describe("auth field gating", () => {
|
||||
const setExt = (ext) => {
|
||||
$config.values.ext = ext;
|
||||
};
|
||||
afterEach(() => {
|
||||
setExt(undefined);
|
||||
});
|
||||
it("shows the password field for default, local and oidc but not ldap or none", () => {
|
||||
expect(new User({ AuthProvider: "default" }).showsPasswordField()).toBe(true);
|
||||
expect(new User({ AuthProvider: "local" }).showsPasswordField()).toBe(true);
|
||||
expect(new User({ AuthProvider: "oidc" }).showsPasswordField()).toBe(true);
|
||||
expect(new User({ AuthProvider: "ldap" }).showsPasswordField()).toBe(false);
|
||||
expect(new User({ AuthProvider: "none" }).showsPasswordField()).toBe(false);
|
||||
});
|
||||
it("requires a password for local, and for default without an external identity", () => {
|
||||
expect(new User({ AuthProvider: "local" }).passwordIsRequired()).toBe(true);
|
||||
// "default" with no OIDC/LDAP configured must require a password (regression:
|
||||
// previously optional, so the ADD button enabled with only a username).
|
||||
expect(new User({ AuthProvider: "default" }).passwordIsRequired()).toBe(true);
|
||||
expect(new User({ AuthProvider: "oidc" }).passwordIsRequired()).toBe(false);
|
||||
expect(new User({ AuthProvider: "ldap" }).passwordIsRequired()).toBe(false);
|
||||
expect(new User({ AuthProvider: "none" }).passwordIsRequired()).toBe(false);
|
||||
});
|
||||
it("makes the password optional for default when external auth can be supplied", () => {
|
||||
setExt({ oidc: { enabled: true } });
|
||||
expect(new User({ AuthProvider: "default" }).passwordIsRequired()).toBe(false);
|
||||
expect(new User({ AuthProvider: "default" }).showsAuthIdField()).toBe(true);
|
||||
});
|
||||
it("shows the auth-id field for oidc and ldap, and for default only when configured", () => {
|
||||
expect(new User({ AuthProvider: "oidc" }).showsAuthIdField()).toBe(true);
|
||||
expect(new User({ AuthProvider: "ldap" }).showsAuthIdField()).toBe(true);
|
||||
expect(new User({ AuthProvider: "default" }).showsAuthIdField()).toBe(false);
|
||||
setExt({ ldap: { enabled: true } });
|
||||
expect(new User({ AuthProvider: "default" }).showsAuthIdField()).toBe(true);
|
||||
expect(new User({ AuthProvider: "local" }).showsAuthIdField()).toBe(false);
|
||||
expect(new User({ AuthProvider: "none" }).showsAuthIdField()).toBe(false);
|
||||
});
|
||||
it("requires the auth-id only for an explicit oidc account", () => {
|
||||
expect(new User({ AuthProvider: "oidc" }).authIdIsRequired()).toBe(true);
|
||||
setExt({ oidc: { enabled: true } });
|
||||
expect(new User({ AuthProvider: "default" }).authIdIsRequired()).toBe(false);
|
||||
expect(new User({ AuthProvider: "ldap" }).authIdIsRequired()).toBe(false);
|
||||
});
|
||||
it("treats the auth-id as a DN for ldap, and for default when only LDAP is configured", () => {
|
||||
expect(new User({ AuthProvider: "ldap" }).authIdIsDn()).toBe(true);
|
||||
expect(new User({ AuthProvider: "oidc" }).authIdIsDn()).toBe(false);
|
||||
setExt({ ldap: { enabled: true } });
|
||||
expect(new User({ AuthProvider: "default" }).authIdIsDn()).toBe(true);
|
||||
setExt({ oidc: { enabled: true }, ldap: { enabled: true } });
|
||||
expect(new User({ AuthProvider: "default" }).authIdIsDn()).toBe(false);
|
||||
});
|
||||
it("labels the auth-id field from authIdIsDn", () => {
|
||||
expect(new User({ AuthProvider: "ldap" }).authIdFieldLabel()).toBe("Distinguished Name (DN)");
|
||||
expect(new User({ AuthProvider: "oidc" }).authIdFieldLabel()).toBe("Subject ID");
|
||||
});
|
||||
it("requires at least one credential for default with OIDC-only configured", () => {
|
||||
// No external auth: the per-field password rule already covers it.
|
||||
expect(new User({ AuthProvider: "default" }).hasLoginCredential()).toBe(true);
|
||||
setExt({ oidc: { enabled: true } });
|
||||
// default + OIDC only, neither password nor Subject ID → inert, blocked.
|
||||
expect(new User({ AuthProvider: "default" }).hasLoginCredential()).toBe(false);
|
||||
expect(new User({ AuthProvider: "default", Password: "secret123" }).hasLoginCredential()).toBe(true);
|
||||
expect(new User({ AuthProvider: "default", AuthID: "sub-123" }).hasLoginCredential()).toBe(true);
|
||||
});
|
||||
it("treats the username as sufficient when LDAP can resolve the account", () => {
|
||||
setExt({ ldap: { enabled: true } });
|
||||
// default + LDAP: the username resolves the account via the directory.
|
||||
expect(new User({ AuthProvider: "default" }).hasLoginCredential()).toBe(true);
|
||||
// explicit ldap: a DN is optional, so a username-only account is allowed.
|
||||
expect(new User({ AuthProvider: "ldap" }).hasLoginCredential()).toBe(true);
|
||||
});
|
||||
it("defers to the per-field rules for non-default providers", () => {
|
||||
setExt({ oidc: { enabled: true } });
|
||||
expect(new User({ AuthProvider: "local" }).hasLoginCredential()).toBe(true);
|
||||
expect(new User({ AuthProvider: "oidc" }).hasLoginCredential()).toBe(true);
|
||||
expect(new User({ AuthProvider: "none" }).hasLoginCredential()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue