Class PushCredentials

java.lang.Object
com.iizix.server.push.engine.http.PushCredentials
All Implemented Interfaces:
IPushUserRegistry, AutoCloseable

public final class PushCredentials extends Object implements IPushUserRegistry, AutoCloseable
The Push Notification (PN) credentials of PUSH-CREDENTIALS.md: the credentials file (sections 2 to 6 and 8), the users registered from Java (section 7) and the group definitions the database users are matched against (section 4).

Location (section 2): the system property "iizi.push.credentials", else the environment variable "IIZI_PUSH_CREDENTIALS"; neither means no file, and the built-in group definitions of section 6 apply. A path that is set but does not exist, cannot be read or is refused for its permissions is SEVERE and the server runs as if no file were configured - it does not fall back to a location nobody configured.

Reload (section 3.5, amended 2026-09-26): the file is watched by the JDK's WatchService through PushFileWatcher - the parent directory registered, a burst of events debounced into one reload, a rename into place seen as a create - never by polling. A reload that finds refused lines applies the valid ones and logs every refusal; a file that cannot be read at reload keeps the previous content in force; a deleted file drops every file user with a WARNING and keeps the last load's group definitions, so that a deletion never widens what the database users may do.

Built-ins replaced entirely (section 6): once a file has loaded, its [groups] are the only group definitions; nothing is merged, and a database group the file does not define grants nothing.

The [server] section (section 3.6, ruled 2026-09-26) configures application ID *; getServerSection() hands it to ServerPushKeys, which applies the precedence of S-4. Its fcmServiceAccountFile names Google's service-account key file and, since PUSH-KEYFILES-DESIGN.md (rulings K-7, K-8), its VPrivKeyFile names a VAPID key file: each a path, %USERPROFILE% and ~ expanded, a relative one under the server's key directory; each read through the engine's PushKeyFiles when a send needs it (S-3), held until the engine's watcher reports it changed, and never logged: a file that is missing, refused for its permissions or unusable is SEVERE once, naming the path, and that transport for * is then not configured. A reload whose [server] VAPID pair differs from the previous one logs the WARNING of S-5, because every existing * Browser subscription is bound to the old pair.

Secrets (section 8): no log line carries a password, a hash, a line of the file or anything of the key file. The permission rule of section 8 - the POSIX refusal and the Windows ACL WARNING - and the path expansion of S-3 live in PushKeyFiles since PUSH-KEYFILES-DESIGN.md, which applies them to every key file; the credentials file is checked by the same code. The weak-entry WARNING of section 3.4 carries a count and no user ID; the messages that concern one user name the user ID and nothing else.

Thread-safe: the file content is an immutable snapshot swapped at load, read without a lock; loads and the registry are synchronized.

Author:
Christopher Mindus
  • Field Details

    • PROPERTY

      public static final String PROPERTY
      The system property naming the file: "iizi.push.credentials". Wins over the environment variable.
      See Also:
    • ENVIRONMENT

      public static final String ENVIRONMENT
      The environment variable naming the file: "IIZI_PUSH_CREDENTIALS". Read when the property is absent.
      See Also:
    • THREAD_NAME

      public static final String THREAD_NAME
      The name of the watcher thread: "iizi-push-credentials".
      See Also:
    • SERVER_HASHING

      public static final PushCredentials.PasswordHashing SERVER_HASHING
      The server's own hasher: com.iizix.server.user.PasswordHasher, reached through UserAuthentication, exactly what every database password goes through.
    • DATABASE_HAS_USER

      public static final Predicate<String> DATABASE_HAS_USER
      Whether a user name is a database user (IZS.USERS), for the section 4 WARNING; a database that cannot answer counts as no.
  • Constructor Details

    • PushCredentials

      public PushCredentials(Path file, PushCredentials.PasswordHashing hashing, Predicate<String> databaseHasUser)
      Constructor with key files of its own and no VAPID key-file storage, for a caller that names no [server] VPrivKeyFile. Nothing is read until start() or load().
      Parameters:
      file - The credentials file, or null when none is configured.
      hashing - The IZ1 hasher.
      databaseHasUser - Answers whether a user name is a database user, or null to skip the section 4 WARNING.
    • PushCredentials

      public PushCredentials(Path file, PushCredentials.PasswordHashing hashing, Predicate<String> databaseHasUser, PushKeyFiles keyFiles, PushKeyFiles.VapidStorage storage)
      Constructor. Nothing is read until start() or load().
      Parameters:
      file - The credentials file, or null when none is configured.
      hashing - The IZ1 hasher.
      databaseHasUser - Answers whether a user name is a database user, or null to skip the section 4 WARNING.
      keyFiles - The key files: the reader of the section's two key files, shared with the engine.
      storage - Loads a VAPID key file as its $push container, or null when [server] VPrivKeyFile cannot be served.
  • Method Details

    • fromConfiguration

      public static PushCredentials fromConfiguration()
      The production instance: the file from PROPERTY or ENVIRONMENT, the server's hasher and the server's database, key files of its own and no VAPID key-file storage.
      Returns:
      The credentials, not yet loaded.
    • fromConfiguration

      public static PushCredentials fromConfiguration(PushKeyFiles keyFiles, PushKeyFiles.VapidStorage storage)
      The production instance over the engine's key files: the file from PROPERTY or ENVIRONMENT, the server's hasher and the server's database.
      Parameters:
      keyFiles - The key files, shared with the engine.
      storage - Loads a VAPID key file as its $push container, or null.
      Returns:
      The credentials, not yet loaded.
    • configuredPath

      public static Path configuredPath()
      Resolves the configured file: the system property, else the environment variable.
      Returns:
      The path, or null when neither is set.
    • getFile

      public Path getFile()
      Gets the configured file.
      Returns:
      The file, or null when none is configured.
    • isFileConfigured

      public boolean isFileConfigured()
      Tells whether a file is configured, whether or not it has loaded.
      Returns:
      true when a path is set.
    • isFileInForce

      public boolean isFileInForce()
      Tells whether a file's definitions are in force: a file has loaded at least once, so its groups replace the built-ins (section 6).
      Returns:
      true once a file has loaded.
    • getFileUserCount

      public int getFileUserCount()
      Gets the number of file users as last loaded.
      Returns:
      The count.
    • getLoadCount

      public int getLoadCount()
      Gets the number of completed loads, initial and on change, whatever their outcome.
      Returns:
      The count.
    • getWatcher

      public PushFileWatcher getWatcher()
      Gets the watcher.
      Returns:
      The watcher, null when no file is configured or before start().
    • getServiceAccountFile

      public Path getServiceAccountFile()
      Gets the expanded path of the service-account key file.
      Returns:
      The path, or null when [server] names none.
    • getVapidKeyFile

      public Path getVapidKeyFile()
      Gets the resolved path of the [server] VAPID key file.
      Returns:
      The path, or null when [server] names none.
    • getKeyFiles

      public PushKeyFiles getKeyFiles()
      Gets the key files.
      Returns:
      The reader, never null.
    • start

      public void start()
      Loads the file and starts watching it through a watcher of its own. Without a configured file nothing runs and no thread is started.
    • start

      public void start(PushCredentials.WatcherSource shared)
      Loads the file and watches it through the engine's watcher, created at this first file to watch; the key files the section names are watched through the same one, from their first read. Without a configured file nothing runs and nothing is watched.
      Parameters:
      shared - Supplies the engine's watcher, or null for a watcher of the credentials' own, closed by close().
    • close

      public void close()
      Stops watching: a watcher of the credentials' own is closed, the engine's is left to the engine. The last content stays in force.
      Specified by:
      close in interface AutoCloseable
    • load

      public void load()
      Loads the file once, as start() does first; for a caller that watches nothing.
    • reload

      public void reload()
      Reloads the file after a change.
    • authenticate

      public PushHttpHandler.AuthUser authenticate(String userName, String password, boolean development) throws PushError
      Authenticates a program user: one of the file (section 3) or registered from Java (section 7), the file winning. A user found here is authenticated here and nowhere else (PUSH-WIRE-CONTRACT 3.2): a wrong password is 401, never a fall-through to the database. Every comparison is constant-time.
      Parameters:
      userName - The user name presented.
      password - The clear-text password presented.
      development - true on a development server, where the built-in default also grants send; matters only while no file is in force.
      Returns:
      The caller, with PushHttpHandler.NO_UID and the union of its groups' privileges; null when the name is neither in the file nor registered, so the caller continues with the database.
      Throws:
      PushError - 401 when the user is known here and the password is wrong.
    • privilegesOfGroups

      public Set<PushPrivilege> privilegesOfGroups(Collection<String> groupNames, boolean development)
      Gets the union of the privileges a set of groups grants: under the file's [groups] when a file is in force, under the built-in definitions of section 6 otherwise. Used for the database users (section 4) and the registered users.
      Parameters:
      groupNames - The group names, in any case.
      development - true on a development server (built-ins only).
      Returns:
      The privileges, never null.
    • getServerSection

      public PushServerSection getServerSection()
      Gets the [server] section as last loaded, its key files read through the reader when the section names them and a send has not read them yet: the service-account JSON (S-3) and the VAPID pair of VPrivKeyFile (K-7), the latter checked against VPubKey when the section sets one. A named file that cannot be used leaves its member null, which ServerPushKeys turns into "not configured" for that transport.
      Returns:
      The section, PushServerSection.EMPTY when the file has none.
    • registerPushUser

      public boolean registerPushUser(String userID, String clearTextPassword, Set<String> groups)
      --- The registry (section 7) ---
      Specified by:
      registerPushUser in interface IPushUserRegistry
      Parameters:
      userID - The user ID: at least one character, no whitespace, no colon.
      clearTextPassword - The password the program will send with Basic authentication.
      groups - The groups whose privileges the user gets, as defined in the credentials file or, without one, by the built-in definitions.
      Returns:
      true if registered; false, with a WARNING naming the user ID, if the user ID is invalid, is already in the credentials file or is already registered - the credentials file always wins.
    • unregisterPushUser

      public boolean unregisterPushUser(String userID)
      Description copied from interface: IPushUserRegistry
      Removes a user registered with IPushUserRegistry.registerPushUser(String, String, Set). Users of the credentials file are not affected.
      Specified by:
      unregisterPushUser in interface IPushUserRegistry
      Parameters:
      userID - The user ID.
      Returns:
      true if a registered user was removed, false if none was registered under that ID.
    • isRegistered

      public boolean isRegistered(String userID)
      Tells whether a user is registered from Java.
      Parameters:
      userID - The user ID, in any case.
      Returns:
      true when registered.
    • toString

      public String toString()
      A summary for the log: the file and the counts, never a secret.
      Overrides:
      toString in class Object
      Returns:
      The summary.
    • getGroupNames

      public Set<String> getGroupNames()
      Gets the group names a file defines, for diagnostics.
      Returns:
      The lower-case names, unmodifiable.
    • getFileUserKeys

      public Set<String> getFileUserKeys()
      Gets the lower-case IDs of the file users, for diagnostics.
      Returns:
      The keys, unmodifiable.
    • getMembers

      public Map<String, Set<String>> getMembers()
      Gets a map of the members as last loaded, for diagnostics.
      Returns:
      Lower-case user ID to lower-case group names, unmodifiable.