TemporaryAccountPool.java
/*
** Module : TemporaryAccountPool.java
** Abstract : temporary account pool implementation.
**
** Copyright (c) 2013-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- -------------------------------Description------------------------------------
** 001 MAG 20131212 Created initial version.
** 002 CA 20140206 Read the temporary client pool size and timeout here, as these are specific
** to this class.
** 003 MAG 20140217 Fixed shut down code.
** 004 IAS 20160403 Replaced ConcurrentLinkedQueue with LinkedBlockingQueue, removed unnecessary
** synchronization
** 005 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 006 DDF 20230620 Replaced static initialization of values from the directory configuration with
** a method called at server bootstrap.
** 007 GBB 20250404 Fixed the order or reading configs and initializing.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.main;
import java.util.concurrent.*;
import java.util.logging.*;
import com.goldencode.p2j.admin.*;
import com.goldencode.p2j.security.*;
import com.goldencode.p2j.util.logging.CentralLogger;
import com.goldencode.p2j.util.*;
import com.goldencode.util.*;
/**
* A temporary account pool implementation.
* On server start-up all temporary accounts which belongs to "temps" group are deleted.
* New accounts are created and added to pool. The new accounts are created disabled.
* Because the password is stored in directory as a hash string, the clear text password
* is kept in memory (pool) inside a <code>TemporaryAccount</code> structure.
* When an item is requested from pool the item is removed form pool and the associated
* account is enabled before returned to caller.
* If the pool is empty the current thread will wait for an specified amount of time to
* get an item returned to pool by other threads.
* When an item is returned to pool first the associated account is disabled than the item
* is enqueued at the tail of the pool and other threads waiting for an item are notified.
* In order to manipulate user accounts special permissions are mandatory.
* A dedicated worker thread is spawned on start-up and is used to manage user accounts at
* runtime. This daemon thread is running on server security context having admin permissions.
*
* @author mag
*
*/
public class TemporaryAccountPool
{
/** Logger. */
private static final CentralLogger LOG = CentralLogger.get(TemporaryAccountWorker.class.getName());
/** Temporary accounts group name */
private static final String TEMP_GROUP = "temps";
/** Random generator length */
public static final int GEN_LENGTH = 16;
/** Random generator digits */
public static final int GEN_DIGITS = 8;
/** Random generator symbols */
public static final int GEN_SYMBOLS = 4;
/** FIFO Pool */
private static final BlockingQueue<TemporaryAccount> pool =
new LinkedBlockingQueue<>();
/** Pool Manager instance */
private static volatile TemporaryAccountPool poolManager;
/** Task used to change the user account status */
private static TemporaryAccountWorker poolTask;
/** poolTask guard */
private static final Object taskLock = new Object();
/** A daemon worker thread having a security context which allow to change user account. */
private static AssociatedThread worker;
/** Timeout in milliseconds. */
private static int timeout = 30000;
/** Pool size */
private static int poolSize = 8;
/**
* Initialize pool. This happens on server start-up.
* All temporary accounts are deleted a new accounts are created.
*/
private TemporaryAccountPool()
{
// Pool size
poolSize = ConfigItem.TEMP_CLIENT_POOL_SIZE.read(poolSize, Utils.DirScope.SERVER);
// Pool timeout
timeout = ConfigItem.TEMP_CLIENT_TIMEOUT.read(timeout, Utils.DirScope.SERVER);
AdminServerImpl.setTargetLive(true);
// Create TEMP_GROUP group if not exists
createGroupIfNotExists();
// Delete temporary accounts
deleteTemporaryAccounts();
// Create new temporary accounts
createTemporaryAccounts();
// Test if pool has been created
if (pool.isEmpty())
{
String message = "Admin privileges are mandatory to create temporary account pool.";
LOG.logp(Level.WARNING,
"TemporaryAccountPool",
"static initializer",
message);
}
else
{
// Refresh target
AdminServerImpl.targetRefresh();
}
// Start worker thread
startUp();
}
/**
* Create new temporary accounts
*/
private static void createTemporaryAccounts()
{
for (int i = 0;i < poolSize;i++)
{
TemporaryAccount account = createUser(false);
if (account != null)
{
pool.add(account);
}
}
}
/**
* Delete all temporary accounts.
* Temporary accounts belong to TEMP_GROUP group.
*/
private static void deleteTemporaryAccounts()
{
TaggedName[] users = AdminServerImpl.listGroupUsers(TEMP_GROUP);
if (users != null)
{
for (TaggedName name : users)
{
UserDef user = AdminServerImpl.getUser(name.getName());
if (user != null)
{
AdminServerImpl.deleteUser(user.subjectId);
}
}
}
}
/**
* Check if temporary group exists. If not create group.
*/
private static void createGroupIfNotExists()
{
GroupDef groupDef = AdminServerImpl.getGroup(TEMP_GROUP);
if (groupDef == null)
{
groupDef = new GroupDef();
groupDef.subjectId = TEMP_GROUP;
groupDef.description = "all temps";
AdminServerImpl.addGroup(groupDef);
}
}
/**
* Create a temporary user.
*
* @param enabled
* Initial account status.
*
* @return Created user account.
*/
static TemporaryAccount createUser(boolean enabled)
{
TemporaryAccount account = null;
// Generate random values for account
String subject = RandomWordGenerator.create(GEN_LENGTH, GEN_DIGITS, 0);
String password = RandomWordGenerator.create(GEN_LENGTH, GEN_DIGITS, GEN_SYMBOLS);
// Fill account structure
UserDef user = new UserDef();
user.enabled = enabled;
user.protect = true;
user.person = "chui";
user.subjectId = subject;
user.password = HashPassword.hashPassword(password);
user.mode = 1;
user.groups = new String[] { TEMP_GROUP };
// Create temporary account
if (AdminServerImpl.addUser(user, null))
{
account = new TemporaryAccount(user.subjectId, password);
}
// Return created account.
return account;
}
/**
* Create and return the pool manager instance.
* This is a singleton design pattern.
*
* @return An instance of TemporaryAccountPool
*/
public static TemporaryAccountPool getInstance()
{
return ManagerHolder.INSTANCE;
}
/**
* Startup worker thread.
*/
private static void startUp()
{
synchronized (taskLock)
{
poolTask = new TemporaryAccountWorker();
worker = new AssociatedThread(poolTask);
worker.start();
}
}
/**
* Shut down worker thread.
*/
public static void shutDown()
{
synchronized (taskLock)
{
poolTask.terminate();
// wait for thread to die
try
{
worker.join(5000);
}
catch (InterruptedException e)
{
LOG.logp(Level.WARNING,
"TemporaryAccountPool",
"shutDown",
"InterruptedException!",
e);
}
}
}
/**
* Get and remove an item from pool.
* Enable the temporary account before return to caller.
* The item should be returned back to pool when no longer used.
* On empty pool wait for an amount of time to get an item from pool.
* If no items are available after waiting a null value is returned.
*
* @return The item at the head of pool or null if pool is empty and
* no item is available after waiting an amount of time.
*/
public TemporaryAccount poll()
{
long timeout = TemporaryAccountPool.timeout;
TemporaryAccount item = null;
try {
item = TemporaryAccountPool.pool.poll(timeout, TimeUnit.MILLISECONDS);
if (item != null)
{
// Enable account
enableAccount(item, true);
}
}
catch (InterruptedException e)
{
LOG.logp(Level.WARNING,
"TemporaryAccountPool.poll()",
"",
"InterruptedException!",
e);
}
return item;
}
/**
* Disable temporary account and return the item to pool.
* The item is enqueued at the tail of the pool.
* Notify other threads waiting for a pool item.
*
* @param item
* A TemporaryAccount item returned to pool.
*/
public void tail(TemporaryAccount item)
{
// Change user account status
enableAccount(item, false);
TemporaryAccountPool.pool.add(item);
}
/**
* Enable / disable temporary account.
*
* @param item
* Account pool item.
* @param enabled
* <code>true</code> enable account.
* <code>false</code> disable account.
*/
private void enableAccount(TemporaryAccount item, boolean enabled)
{
// Change user account status
Runnable core = new UpdateAccountTask(item, enabled);
synchronized (taskLock)
{
TemporaryAccountPool.poolTask.execute(core);
}
}
/**
* Helper clasee for the lazt initialization of the TemporaryAccountPool instance
*/
private static class ManagerHolder
{
/** TemporaryAccountPool instance will be initialized on the first access */
public static final TemporaryAccountPool INSTANCE = new TemporaryAccountPool();
}
}