All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jetty.security.PropertyUserStore Maven / Gradle / Ivy

There is a newer version: 2.0.32
Show newest version
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.security;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.eclipse.jetty.io.IOResources;
import org.eclipse.jetty.util.Scanner;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.Resources;
import org.eclipse.jetty.util.security.Credential;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 

This class monitors a property file of the format mentioned below * and notifies registered listeners of the changes to the the given file.

* *
 *  username: password [,rolename ...]
 * 
* *

Passwords may be clear text, obfuscated or checksummed. * The class {@link org.eclipse.jetty.util.security.Password} should be used * to generate obfuscated passwords or password checksums.

* *

If DIGEST Authentication is used, the password must be in a recoverable * format, either plain text or obfuscated.

*/ public class PropertyUserStore extends UserStore implements Scanner.DiscreteListener { private static final Logger LOG = LoggerFactory.getLogger(PropertyUserStore.class); protected Resource _configResource; protected Scanner _scanner; protected int _reloadInterval = 0; protected boolean _firstLoad = true; // true if first load, false from that point on protected List _listeners; /** * Get the config (as a string) * * @return the config path as a string */ public Resource getConfig() { return _configResource; } /** * Set the Config Path from a String reference to a file * * @param config the config file * TODO: reintroduce setConfig(String) and internal ResourceFactory usage */ public void setConfig(Resource config) { _configResource = config; } /** * @return the resource associated with the configured properties file, creating it if necessary * @deprecated */ @Deprecated(forRemoval = true) public Resource getConfigResource() { return getConfig(); } /** * Is hot reload enabled on this user store * * @return true if hot reload was enabled before startup * @deprecated use {@link #getReloadInterval()} */ @Deprecated public boolean isHotReload() { return getReloadInterval() > 0; } /** * Enable Hot Reload of the Property File * * @param enable true to enable to a 1 second scan, false to disable * @deprecated use {@link #setReloadInterval(int)} */ @Deprecated public void setHotReload(boolean enable) { setReloadInterval(enable ? 1 : 0); } /** * Enable Hot Reload of the Property File * * @param scanSeconds the period in seconds to scan for property file changes, or 0 for no scanning */ public void setReloadInterval(int scanSeconds) { if (isRunning()) { throw new IllegalStateException("Cannot set scan period while user store is running"); } this._reloadInterval = scanSeconds; } /** * Get the period in seconds to scan for property file changes, or 0 for no scanning. * @return the period in seconds to scan for property file changes, or 0 for no scanning */ public int getReloadInterval() { return _reloadInterval; } @Override public String toString() { return String.format("%s[cfg=%s]", super.toString(), _configResource); } /** * Load the user data from the property file. * @throws IOException If the users cannot be loaded */ protected void loadUsers() throws IOException { Resource config = getConfig(); if (config == null) throw new IllegalStateException("No config path set"); if (LOG.isDebugEnabled()) LOG.debug("Loading {} from {}", this, config); if (Resources.missing(config)) throw new IllegalStateException("Config does not exist: " + config); Properties properties = new Properties(); try (InputStream inputStream = IOResources.asInputStream(config)) { properties.load(inputStream); } Set known = new HashSet<>(); for (Map.Entry entry : properties.entrySet()) { String username = ((String)entry.getKey()).trim(); String credentials = ((String)entry.getValue()).trim(); String roles = null; int c = credentials.indexOf(','); if (c >= 0) { roles = credentials.substring(c + 1).trim(); credentials = credentials.substring(0, c).trim(); } if (username.length() > 0) { String[] roleArray = new String[0]; if (roles != null && roles.length() > 0) roleArray = StringUtil.csvSplit(roles); known.add(username); Credential credential = Credential.getCredential(credentials); addUser(username, credential, roleArray); notifyUpdate(username, credential, roleArray); } } List currentlyKnownUsers = new ArrayList<>(_users.keySet()); // if its not the initial load then we want to process removed users if (!_firstLoad) { for (String user : currentlyKnownUsers) { if (!known.contains(user)) { removeUser(user); notifyRemove(user); } } } // set initial load to false as there should be no more initial loads _firstLoad = false; if (LOG.isDebugEnabled()) LOG.debug("Loaded {} from {}", this, config); } /** * Depending on the value of the refresh interval, this method will either start * up a scanner thread that will monitor the properties file for changes after * it has initially loaded it. Otherwise the users will be loaded and there will * be no active monitoring thread so changes will not be detected. */ @Override protected void doStart() throws Exception { Resource config = getConfig(); if (getReloadInterval() > 0 && (config != null)) { _scanner = new Scanner(null, false); _scanner.addFile(config.getPath()); _scanner.setScanInterval(_reloadInterval); _scanner.setReportExistingFilesOnStartup(false); _scanner.addListener(this); addBean(_scanner); } loadUsers(); super.doStart(); } @Override public void pathChanged(Path path) throws Exception { loadUsers(); } @Override public void pathAdded(Path path) throws Exception { loadUsers(); } @Override public void pathRemoved(Path path) throws Exception { loadUsers(); } @Override protected void doStop() throws Exception { super.doStop(); removeBean(_scanner); _scanner = null; } /** * Notifies the registered listeners of potential updates to a user * * @param username the user that was updated * @param credential the updated credentials * @param roleArray the updated roles */ private void notifyUpdate(String username, Credential credential, String[] roleArray) { if (_listeners != null) { for (UserListener listener : _listeners) { listener.update(username, credential, roleArray); } } } /** * Notifies the registered listeners that a user has been removed. * * @param username the user that was removed */ private void notifyRemove(String username) { if (_listeners != null) { for (UserListener listener : _listeners) { listener.remove(username); } } } /** * Registers a listener to be notified of the contents of the property file * * @param listener the user listener */ public void registerUserListener(UserListener listener) { if (_listeners == null) _listeners = new ArrayList<>(); _listeners.add(listener); } public interface UserListener { void update(String username, Credential credential, String[] roleArray); void remove(String username); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy