PrometheusControlKey.java
/*
* Prometheus: Application Framework
* Copyright 2012-2026. Tony Washer
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package io.github.tonywasher.joceanus.prometheus.data;
import io.github.tonywasher.joceanus.gordianknot.api.factory.GordianFactory;
import io.github.tonywasher.joceanus.gordianknot.api.factory.GordianFactory.GordianFactoryLock;
import io.github.tonywasher.joceanus.gordianknot.util.GordianUtilities;
import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataList;
import io.github.tonywasher.joceanus.metis.data.MetisDataResource;
import io.github.tonywasher.joceanus.metis.field.MetisFieldItem;
import io.github.tonywasher.joceanus.metis.field.MetisFieldSet;
import io.github.tonywasher.joceanus.metis.field.MetisFieldVersionedSet;
import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
import io.github.tonywasher.joceanus.oceanus.date.OceanusDate;
import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
import io.github.tonywasher.joceanus.prometheus.data.PrometheusControlKeySet.PrometheusControlKeySetList;
import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataSet.PrometheusCryptographyDataType;
import io.github.tonywasher.joceanus.prometheus.exc.PrometheusDataException;
import io.github.tonywasher.joceanus.prometheus.security.PrometheusSecurityPasswordManager;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* ControlKey definition and list. The Control Key represents the passwordHash that controls
* securing of the dataKeys. It maintains a map of the associated DataKeys.
*
* @author Tony Washer
*/
public final class PrometheusControlKey
extends PrometheusDataItem {
/**
* Object name.
*/
public static final String OBJECT_NAME = PrometheusCryptographyDataType.CONTROLKEY.getItemName();
/**
* List name.
*/
public static final String LIST_NAME = PrometheusCryptographyDataType.CONTROLKEY.getListName();
/**
* KeySetHash Length.
*/
public static final int LOCKLEN = GordianUtilities.getFactoryLockLen();
/**
* Report fields.
*/
private static final MetisFieldVersionedSet<PrometheusControlKey> FIELD_DEFS = MetisFieldVersionedSet.newVersionedFieldSet(PrometheusControlKey.class);
/*
* FieldIds.
*/
static {
FIELD_DEFS.declareByteArrayField(PrometheusDataResource.CONTROLKEY_LOCKBYTES, LOCKLEN);
FIELD_DEFS.declareDateField(PrometheusDataResource.CONTROLKEY_CREATION);
FIELD_DEFS.declareDerivedVersionedField(PrometheusDataResource.CONTROLKEY_LOCK);
FIELD_DEFS.declareLocalField(PrometheusDataResource.CONTROLKEYSET_LIST, PrometheusControlKey::getControlKeySets);
}
/**
* Name of Database.
*/
public static final String NAME_DATABASE = PrometheusDataResource.CONTROLKEY_DATABASE.getValue();
/**
* The DataKeySetCache.
*/
private ControlKeySetCache theKeySetCache = new ControlKeySetCache();
/**
* Copy constructor.
*
* @param pList the List to add to
* @param pSource the source key to copy
*/
private PrometheusControlKey(final PrometheusControlKeyList pList,
final PrometheusControlKey pSource) {
/* Initialise the item */
super(pList, pSource);
}
/**
* Values constructor.
*
* @param pList the List to add to
* @param pValues the values constructor
* @throws OceanusException on error
*/
private PrometheusControlKey(final PrometheusControlKeyList pList,
final PrometheusDataValues pValues) throws OceanusException {
/* Initialise the item */
super(pList, pValues);
/* Protect against exceptions */
try {
/* Store FactoryLock */
Object myValue = pValues.getValue(PrometheusDataResource.CONTROLKEY_LOCKBYTES);
if (myValue instanceof byte[] ba) {
setValueFactoryLockBytes(ba);
}
/* Store/Resolve Hash */
myValue = pValues.getValue(PrometheusDataResource.CONTROLKEY_LOCK);
if (myValue instanceof GordianFactoryLock l) {
setValueFactoryLock(l);
} else if (getLockBytes() != null) {
/* Access the Security manager */
final PrometheusDataSet myData = getDataSet();
final PrometheusSecurityPasswordManager myPasswordMgr = myData.getPasswordMgr();
/* Resolve the factoryLock */
final GordianFactoryLock myLock = myPasswordMgr.resolveFactoryLock(getLockBytes(), NAME_DATABASE);
/* Store the factoryLock */
setValueFactoryLock(myLock);
}
/* Store the CreationDate */
myValue = pValues.getValue(PrometheusDataResource.CONTROLKEY_CREATION);
if (!(myValue instanceof OceanusDate)) {
myValue = new OceanusDate();
}
setValueCreationDate((OceanusDate) myValue);
/* Catch Exceptions */
} catch (OceanusException e) {
/* Pass on exception */
throw new PrometheusDataException(this, ERROR_CREATEITEM, e);
}
}
/**
* Constructor for a new ControlKey.
* <p>
* This will create a new DataKeySet with a new set of DataKeys.
*
* @param pList the list to which to add the key to
* @throws OceanusException on error
*/
private PrometheusControlKey(final PrometheusControlKeyList pList) throws OceanusException {
/* Initialise the item */
super(pList, 0);
/* Protect against exceptions */
try {
/* Access the Security manager */
final PrometheusDataSet myData = getDataSet();
final PrometheusSecurityPasswordManager myPasswordMgr = myData.getPasswordMgr();
/* Create a new factoryLock with new password */
final GordianFactoryLock myLock = myPasswordMgr.newFactoryLock(NAME_DATABASE);
/* Store the factoryLock */
setValueFactoryLockBytes(myLock.getLockBytes());
setValueFactoryLock(myLock);
/* Allocate the DataKeySets */
allocateControlKeySets(myData);
/* Set the creationDate */
setValueCreationDate(new OceanusDate());
/* Catch Exceptions */
} catch (OceanusException e) {
/* Pass on exception */
throw new PrometheusDataException(this, ERROR_CREATEITEM, e);
}
}
/**
* Constructor for a new ControlKey with the same password.
* <p>
* This will create a new DataKeySet with a new cloned set of DataKeys.
*
* @param pKey the key to copy
* @throws OceanusException on error
*/
private PrometheusControlKey(final PrometheusControlKey pKey) throws OceanusException {
/* Initialise the item */
super(pKey.getList(), 0);
/* Protect against exceptions */
try {
/* Access the Security manager */
final PrometheusDataSet myData = getDataSet();
final PrometheusSecurityPasswordManager myPasswordMgr = myData.getPasswordMgr();
/* ReSeed the security generator */
final GordianFactory myFactory = myPasswordMgr.getSecurityFactory();
myFactory.reSeedRandom();
/* Create a similar factoryLock */
final GordianFactoryLock myLock = myPasswordMgr.similarFactoryLock(pKey.getFactoryLock());
/* Store the factoryLock */
setValueFactoryLockBytes(myLock.getLockBytes());
setValueFactoryLock(myLock);
/* Allocate the ControlKeySets */
allocateControlKeySets(myData);
/* Catch Exceptions */
} catch (OceanusException e) {
/* Pass on exception */
throw new PrometheusDataException(this, ERROR_CREATEITEM, e);
}
}
@Override
public MetisFieldSetDef getDataFieldSet() {
return FIELD_DEFS;
}
/**
* Obtain the dataKeySetCache.
*
* @return the dataKeySets
*/
private ControlKeySetCache getControlKeySets() {
return theKeySetCache;
}
/**
* Get the LockBytes.
*
* @return the hash bytes
*/
public byte[] getLockBytes() {
return getValues().getValue(PrometheusDataResource.CONTROLKEY_LOCKBYTES, byte[].class);
}
/**
* Get the factoryLock.
*
* @return the factoryLock
*/
public GordianFactoryLock getFactoryLock() {
return getValues().getValue(PrometheusDataResource.CONTROLKEY_LOCK, GordianFactoryLock.class);
}
/**
* Get the securityFactory.
*
* @return the securityFactory
*/
public GordianFactory getSecurityFactory() {
final GordianFactoryLock myLock = getFactoryLock();
return myLock == null ? null : myLock.getFactory();
}
/**
* Get the CreationDate.
*
* @return the creationDate
*/
public OceanusDate getCreationDate() {
return getValues().getValue(PrometheusDataResource.CONTROLKEY_CREATION, OceanusDate.class);
}
/**
* Set the FactoryLock Bytes.
*
* @param pValue the factoryLock bytes
* @throws OceanusException on error
*/
private void setValueFactoryLockBytes(final byte[] pValue) throws OceanusException {
getValues().setValue(PrometheusDataResource.CONTROLKEY_LOCKBYTES, pValue);
}
/**
* Set the FactoryLock.
*
* @param pValue the factoryLock
* @throws OceanusException on error
*/
private void setValueFactoryLock(final GordianFactoryLock pValue) throws OceanusException {
getValues().setValue(PrometheusDataResource.CONTROLKEY_LOCK, pValue);
}
/**
* Set the CreationDate.
*
* @param pValue the creationDate
* @throws OceanusException on error
*/
private void setValueCreationDate(final OceanusDate pValue) throws OceanusException {
getValues().setValue(PrometheusDataResource.CONTROLKEY_CREATION, pValue);
}
@Override
public PrometheusControlKey getBase() {
return (PrometheusControlKey) super.getBase();
}
@Override
public PrometheusControlKeyList getList() {
return (PrometheusControlKeyList) super.getList();
}
/**
* Obtain the next DataKeySet.
*
* @return the next dataKeySet
*/
PrometheusDataKeySet getNextDataKeySet() {
return theKeySetCache.getNextDataKeySet();
}
@Override
public int compareValues(final PrometheusDataItem pThat) {
/* Only sort on id */
return 0;
}
/**
* Allocate a new ControlKeySet.
*
* @param pData the DataSet
* @throws OceanusException on error
*/
private void allocateControlKeySets(final PrometheusDataSet pData) throws OceanusException {
/* Access the ControlKeySet List */
final PrometheusControlKeySetList mySets = pData.getControlKeySets();
setNewVersion();
/* Loop to create sufficient ControlKeySets */
final int myNumKeySets = pData.getNumActiveKeySets();
for (int i = 0; i < myNumKeySets; i++) {
/* Allocate the ControlKeySet */
final PrometheusControlKeySet mySet = new PrometheusControlKeySet(mySets, this);
mySet.setNewVersion();
mySets.add(mySet);
/* Register the DataKeySet */
theKeySetCache.registerControlKeySet(mySet);
}
}
/**
* Delete the old set of ControlKey and DataKeys.
*/
void deleteControlSet() {
/* Delete the ControlKeySet */
theKeySetCache.deleteControlKeySets();
/* Mark this control key as deleted */
setDeleted(true);
}
/**
* Update factoryLock.
*
* @param pSource the source of the data
* @throws OceanusException on error
*/
void updateFactoryLock(final String pSource) throws OceanusException {
/* Access the Security manager */
final PrometheusDataSet myData = getDataSet();
final PrometheusSecurityPasswordManager myPasswordMgr = myData.getPasswordMgr();
/* Obtain a new factoryLock */
final GordianFactoryLock myLock = myPasswordMgr.newFactoryLock(getSecurityFactory(), pSource);
/* Store the current detail into history */
pushHistory();
/* Update the factoryLock */
setValueFactoryLock(myLock);
setValueFactoryLockBytes(myLock.getLockBytes());
myData.setVersion(myData.getVersion() + 1);
/* Check for changes */
checkForHistory();
}
/**
* Register ControlKeySet.
*
* @param pKeySet the ControlKeySet to register
*/
void registerControlKeySet(final PrometheusControlKeySet pKeySet) {
/* Store the DataKey into the map */
theKeySetCache.registerControlKeySet(pKeySet);
}
/**
* ControlKey List.
*/
public static class PrometheusControlKeyList
extends PrometheusDataList<PrometheusControlKey> {
/**
* Report fields.
*/
private static final MetisFieldSet<PrometheusControlKeyList> FIELD_DEFS = MetisFieldSet.newFieldSet(PrometheusControlKeyList.class);
/**
* Construct an empty CORE ControlKey list.
*
* @param pData the DataSet for the list
*/
protected PrometheusControlKeyList(final PrometheusDataSet pData) {
this(pData, PrometheusListStyle.CORE);
}
/**
* Construct an empty generic ControlKey list.
*
* @param pData the DataSet for the list
* @param pStyle the style of the list
*/
protected PrometheusControlKeyList(final PrometheusDataSet pData,
final PrometheusListStyle pStyle) {
super(PrometheusControlKey.class, pData, PrometheusCryptographyDataType.CONTROLKEY, pStyle);
}
/**
* Constructor for a cloned List.
*
* @param pSource the source List
*/
private PrometheusControlKeyList(final PrometheusControlKeyList pSource) {
super(pSource);
}
@Override
public MetisFieldSet<PrometheusControlKeyList> getDataFieldSet() {
return FIELD_DEFS;
}
@Override
public String listName() {
return LIST_NAME;
}
@Override
public MetisFieldSet<PrometheusControlKey> getItemFields() {
return PrometheusControlKey.FIELD_DEFS;
}
@Override
public boolean includeDataXML() {
return false;
}
@Override
protected PrometheusControlKeyList getEmptyList(final PrometheusListStyle pStyle) {
final PrometheusControlKeyList myList = new PrometheusControlKeyList(this);
myList.setStyle(pStyle);
return myList;
}
@Override
public PrometheusControlKeyList deriveList(final PrometheusListStyle pStyle) throws OceanusException {
return (PrometheusControlKeyList) super.deriveList(pStyle);
}
@Override
public PrometheusControlKeyList deriveDifferences(final PrometheusDataSet pDataSet,
final PrometheusDataList<?> pOld) {
return (PrometheusControlKeyList) super.deriveDifferences(pDataSet, pOld);
}
@Override
public PrometheusControlKey addCopyItem(final PrometheusDataItem pItem) {
/* Can only clone a ControlKey */
if (!(pItem instanceof PrometheusControlKey)) {
return null;
}
/* Clone the control key */
final PrometheusControlKey myKey = new PrometheusControlKey(this, (PrometheusControlKey) pItem);
add(myKey);
return myKey;
}
@Override
public PrometheusControlKey addNewItem() {
throw new UnsupportedOperationException();
}
@Override
public PrometheusControlKey addValuesItem(final PrometheusDataValues pValues) throws OceanusException {
/* Create the controlKey */
final PrometheusControlKey myKey = new PrometheusControlKey(this, pValues);
/* Check that this keyId has not been previously added */
if (!isIdUnique(myKey.getIndexedId())) {
myKey.addError(ERROR_DUPLICATE, MetisDataResource.DATA_ID);
throw new PrometheusDataException(myKey, ERROR_VALIDATION);
}
/* Add to the list */
add(myKey);
/* Return it */
return myKey;
}
/**
* Create a new ControlKey (with associated DataKeys).
*
* @return the new item
* @throws OceanusException on error
*/
public PrometheusControlKey createNewKeySet() throws OceanusException {
/* Create the key */
final PrometheusControlKey myKey = new PrometheusControlKey(this);
/* Add to the list */
add(myKey);
return myKey;
}
/**
* Add a cloned ControlKey (with associated DataKeys).
*
* @param pSource the source key
* @return the new item
* @throws OceanusException on error
*/
public PrometheusControlKey cloneItem(final PrometheusControlKey pSource) throws OceanusException {
/* Create the key */
final PrometheusControlKey myKey = new PrometheusControlKey(pSource);
/* Add to the list */
add(myKey);
return myKey;
}
/**
* Initialise Security from a DataBase for a SpreadSheet load.
*
* @param pDatabase the DataSet for the Database
* @throws OceanusException on error
*/
protected void initialiseSecurity(final PrometheusDataSet pDatabase) throws OceanusException {
/* Access the active control key from the database */
final PrometheusDataSet myData = getDataSet();
final PrometheusControlKey myDatabaseKey = pDatabase.getControlKey();
final PrometheusControlKey myKey;
/* If we have an existing security key */
if (myDatabaseKey != null) {
/* Clone the Control Key and its DataKeySets */
myKey = cloneControlKey(myDatabaseKey);
/* else create a new security set */
} else {
/* Create the new security set */
myKey = createNewKeySet();
}
/* Declare the Control Key */
myData.getControl().setControlKey(myKey);
}
/**
* Delete old controlKeys.
*/
protected void purgeOldControlKeys() {
/* Access the current control Key */
final PrometheusDataSet myData = getDataSet();
final PrometheusControlKey myKey = myData.getControlKey();
/* Loop through the controlKeys */
final Iterator<PrometheusControlKey> myIterator = iterator();
while (myIterator.hasNext()) {
final PrometheusControlKey myCurr = myIterator.next();
/* Delete if this is not the active key */
if (!myKey.equals(myCurr)) {
myCurr.deleteControlSet();
}
}
}
/**
* Clone ControlKey from dataBase.
*
* @param pControlKey the ControlKey to clone
* @return the new control key
* @throws OceanusException on error
*/
private PrometheusControlKey cloneControlKey(final PrometheusControlKey pControlKey) throws OceanusException {
/* Build data values */
final PrometheusDataValues myValues = new PrometheusDataValues(OBJECT_NAME);
myValues.addValue(MetisDataResource.DATA_ID, pControlKey.getIndexedId());
myValues.addValue(PrometheusDataResource.CONTROLKEY_LOCKBYTES, pControlKey.getLockBytes());
myValues.addValue(PrometheusDataResource.CONTROLKEY_CREATION, pControlKey.getCreationDate());
myValues.addValue(PrometheusDataResource.CONTROLKEY_LOCK, pControlKey.getFactoryLock());
/* Clone the control key */
final PrometheusControlKey myControl = addValuesItem(myValues);
/* Access the ControlKeySet List */
final PrometheusDataSet myData = getDataSet();
final PrometheusControlKeySetList myKeySets = myData.getControlKeySets();
/* Create a new ControlKeySetCache for this ControlKey */
final ControlKeySetCache mySource = pControlKey.getControlKeySets();
myControl.theKeySetCache = mySource.cloneControlKeySetCache(myControl, myKeySets);
/* return the cloned key */
return myControl;
}
@Override
public void postProcessOnLoad() throws OceanusException {
/* Just sort the list */
reSort();
}
@Override
protected PrometheusDataMapItem allocateDataMap() {
/* Unused */
throw new UnsupportedOperationException();
}
}
/**
* ControlKeySetCache.
*/
private static final class ControlKeySetCache
implements MetisFieldItem, MetisDataList<PrometheusControlKeySet> {
/**
* Report fields.
*/
private static final MetisFieldSet<ControlKeySetCache> FIELD_DEFS = MetisFieldSet.newFieldSet(ControlKeySetCache.class);
/*
* Size Field Id.
*/
static {
FIELD_DEFS.declareLocalField(MetisDataResource.LIST_SIZE, ControlKeySetCache::size);
}
/**
* The list.
*/
private final List<PrometheusControlKeySet> theList;
/**
* Iterator.
*/
private Iterator<PrometheusControlKeySet> theIterator;
/**
* Constructor.
*/
ControlKeySetCache() {
theList = new ArrayList<>();
}
@Override
public MetisFieldSet<ControlKeySetCache> getDataFieldSet() {
return FIELD_DEFS;
}
@Override
public List<PrometheusControlKeySet> getUnderlyingList() {
return theList;
}
@Override
public String formatObject(final OceanusDataFormatter pFormatter) {
return getDataFieldSet().getName();
}
/**
* Register the KeySet.
*
* @param pKeySet the KeySet to register
*/
private void registerControlKeySet(final PrometheusControlKeySet pKeySet) {
/* If this is first registration */
if (!theList.contains(pKeySet)) {
/* Add the KeySet */
theList.add(pKeySet);
/* Reset any iterator */
if (theIterator != null) {
theIterator = iterator();
}
}
}
/**
* Get next DataKeySet.
*
* @return the next KeySet
*/
private PrometheusDataKeySet getNextDataKeySet() {
/* Handle empty list */
if (isEmpty()) {
return null;
}
/* Handle initialisation and wrapping */
if (theIterator == null
|| !theIterator.hasNext()) {
theIterator = iterator();
}
/* Return the next KeySet */
return theIterator.next().getNextDataKeySet();
}
/**
* Delete the ControlKeySets.
*/
private void deleteControlKeySets() {
/* Loop through the KeySets */
final Iterator<PrometheusControlKeySet> myIterator = iterator();
while (myIterator.hasNext()) {
final PrometheusControlKeySet mySet = myIterator.next();
/* Delete the KeySet */
mySet.deleteControlKeySet();
}
}
/**
* Clone controlKeySet Cache from a DataBase.
*
* @param pControlKey the ControlKey to clone
* @param pKeySets the ControlKeySetList
* @return the new ControlKeySetCache
* @throws OceanusException on error
*/
private ControlKeySetCache cloneControlKeySetCache(final PrometheusControlKey pControlKey,
final PrometheusControlKeySetList pKeySets) throws OceanusException {
/* Create a new resource */
final ControlKeySetCache myCache = new ControlKeySetCache();
/* Loop through the KeySets */
final Iterator<PrometheusControlKeySet> myIterator = iterator();
while (myIterator.hasNext()) {
final PrometheusControlKeySet mySet = myIterator.next();
/* Create a new ControlKeySet for this ControlKey */
final PrometheusControlKeySet myNewSet = pKeySets.cloneControlKeySet(pControlKey, mySet);
myCache.registerControlKeySet(myNewSet);
}
/* Return the cache */
return myCache;
}
}
}