SecurityPolicyManager.java
/*
** Module : SecurityPolicyManager.java
** Abstract : Provides SECURITY-POLICY attributes and methods support
**
** Copyright (c) 2019-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 IAS 20190502 Created initial version.
** 002 IAS 20190515 Added runtime support for attributes and cryptography.
** 003 IAS 20190619 Added runtime support for GET-CLIENT, SET-CLIENT, REGISTER-DOMAIN,
** LOCK-REGISTRATION, LOAD-DOMAINS.
** 004 IAS 20190703 Changed support for LOAD-DOMAINS, SET-CLIENT, SET-DB-CLIENT
** for non-default domains
** 005 IAS 20191015 Rework SET-CLIENT/SET-DB-CLIENT
** 006 CA 20191119 Disabled the 16388 error in setClient, as it fails if the DBs are already
** locked with the same client-principal.
** 007 IAS 20200115 Added domain access code lookup by domain name
** 008 OM 20201203 Fixed handling of READ/ONLY attributes.
** 009 IAS 20220415 Misc. fixes for error reporting.
** 010 CA 20230116 Avoid using handle.unwrap, handle.getReference or other BDT usage from within FWD runtime.
** 011 OM 20241015 Fixed database compare in multi-tenant case. Import cleanup.
** 012 OM 20241128 Multi-tenant runtime support: selected the proper persistence context, eventually
** based on [sharedDb] parameter.
** 013 OM 20250227 Local optimizations: avoided multiple context lookups, etc.
*/
/*
** 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.util;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import java.util.function.*;
import org.apache.commons.lang3.StringUtils; // Need explicit import to avoid ambiguity
import com.goldencode.p2j.persist.*;
import com.goldencode.p2j.security.*;
import com.goldencode.p2j.util.ClientPrincipal.*;
import com.goldencode.p2j.util.ClientPrincipalResource.*;
import com.goldencode.p2j.util.CryptoUtils.*;
import com.goldencode.p2j.util.SecurityOps.*;
import com.goldencode.proxy.*;
/**
* Provides static methods implementing support for the SECURITY-POLICY system
* handle attributes and methods See {@link CommonSecurityPolicy}
*
*/
public class SecurityPolicyManager
extends SystemHandleBase
{
/**
* Supported symmetric ciphers' list as String.
*/
private static final String CIPHER_NAMES = String.join(",", CryptoUtils.CIPHERS);
/**
* Supported PBE hash algorithms.
*/
private static final Set<String> DIGESTS = Collections.unmodifiableSet(CryptoUtils.DIGESTS.keySet());
/**
* Context local proxy that allows SECURITY-POLICY attributes to be pushed from
* server to the client.
*/
private static final ContextLocal<WorkArea> work = new ContextLocal<WorkArea>()
{
/**
* Initializes the work area, the first time it is requested within a new
* context.
* <p>
* This will obtain a unique instance of <code>SessionExports</code> depending
* on which side (server or client) it is called the returned value can be a
* local or a network proxy instance.
*
* @return The newly instantiated work area.
*/
@Override
protected WorkArea initialValue()
{
return new WorkArea();
}
};
/**
* Get a the instance for the SECURITY-POLICY system handle. Is obtained using a
* call to the {@link StaticProxy#obtain(Class, Class[])}, using the
* {@link CommonSecurityPolicy} interface and its methods implemented by these
* classes: {@link SecurityPolicyManager}.
*
* @return See above.
*/
public static handle asHandle()
{
WorkArea wa = work.get();
Class<?>[] classes = { SecurityPolicyManager.class };
CommonSecurityPolicy proxy = StaticProxy.obtain(CommonSecurityPolicy.class, classes);
if (wa.id == null)
{
wa.id = handle.resourceId(proxy);
}
return new handle(proxy);
}
/**
* Implementation of SECURITY-POLICY:SYMMETRIC-SUPPORT attribute getter. Returns
* a comma-separated list of supported cryptographic algorithm names to use in
* encrypting and decrypting data. Each algorithm name is a concatenation of
* three character expressions that identify an algorithm, mode, and key size.
*
* @return A comma-separated list of supported cryptographic algorithm names.
*/
public static character getSymmetricSupport()
{
return new character(CIPHER_NAMES);
}
/**
* Provides the decoded value of the
* SECURITY-POLICY:SYMMETRIC-ENCRYPTION-ALGORITHM attribute. For internal use
*
* @return the decoded value of the
* SECURITY-POLICY:SYMMETRIC-ENCRYPTION-ALGORITHM attribute.
*/
static CipherParams getSymmetricCiperParams()
{
return new CipherParams(work.get().symmetricEncryptionAlgorithm);
}
/**
* Provides the decoded value of the symmetric encryption algorithm For internal
* use.
*
* @param spec The algorithm spec string. If is is null or empty the value of
* the SECURITY-POLICY:SYMMETRIC-ENCRYPTION-ALGORITHM attribute is
* decoded
*
* @return the decoded value of the symmetric encryption algorithm.
*
* @throws java.lang.IllegalArgumentException if the invalid parameter was
* provided
*/
static CipherParams getSymmetricCipherParams(String spec)
{
return new CipherParams(
StringUtils.isEmpty(spec) ? work.get().symmetricEncryptionAlgorithm : spec);
}
/**
* Implementation of SECURITY-POLICY:SYMMETRIC-ENCRYPTION-ALGORITHM attribute
* getter. This is write-only attribute, so the getter always return error
*
* @return error (4052).
*/
public static character getSymmetricEncryptionAlgorithm()
{
return new character(work.get().symmetricEncryptionAlgorithm);
}
/**
* Implementation of SECURITY-POLICY:SYMMETRIC-ENCRYPTION-ALGORITHM attribute
* setter. Sets the name of the default cryptographic algorithm to use with the
* ENCRYPT and DECRYPT functions.
*
* @param aname The name of the default cryptographic algorithm.
*/
public static void setSymmetricEncryptionAlgorithm(character aname)
{
if (aname.isUnknown() || !CryptoUtils.CIPHERS.contains(StringUtils.upperCase(aname.value)))
{
invalidValue(12223, "SYMMETRIC-ENCRYPTION-ALGORITHM");
return;
}
work.get().symmetricEncryptionAlgorithm = aname.value;
}
/**
* Implementation of SECURITY-POLICY:SYMMETRIC-ENCRYPTION-KEY attribute getter.
* This is write-only attribute, so the getter always return error
*
* @return error (4052).
*/
public static raw getSymmetricEncryptionKey()
{
ErrorManager.displayError(4052,
"SYMMETRIC-ENCRYPTION-KEY is not a queryable attribute for PSEIDO-WIDGET");
return raw.instantiateUnknownRaw();
}
/**
* Implementation of SECURITY-POLICY:SYMMETRIC-ENCRYPTION-ALGORITHM attribute
* setter. Sets the default encryption key (a binary value) to use with the
* ENCRYPT and DECRYPT functions. The default value is the Unknown value (?).
*
* @param key The default encryption key.
*/
public static void setSymmetricEncryptionKey(raw key)
{
work.get().encryptionKey = key.isUnknown() ? null : key.getByteArray();
}
/**
* Implementation of SECURITY-POLICY:SYMMETRIC-ENCRYPTION-IV attribute getter.
* Returns the default initialization vector value to use with the encryption
* key in the ENCRYPT and DECRYPT functions. The default value is the Unknown
* value (?), which indicates that no initialization vector value is used.
*
* @return The default initialization vector value.
*/
public static raw getSymmetricEncryptionIV()
{
byte[] iv = work.get().encryptionIV;
// Contrary to the 4GL doc the default value of this attribute is empty RAW, not UNKNOWN
// And if the UNKNOWN value is assigned the empty RAW is returned as well
return iv == null ? new raw(new byte[0]) : new raw(iv.clone());
}
/**
* Implementation of SECURITY-POLICY:SYMMETRIC-ENCRYPTION-IV attribute setter.
* Sets the default initialization vector value to use with the encryption key
* in the ENCRYPT and DECRYPT functions. The default value is the Unknown value
* (?), which indicates that no initialization vector value is used.
*
* @param iv The default initialization vector value.
*/
public static void setSymmetricEncryptionIV(raw iv)
{
work.get().encryptionIV = iv.isUnknown() ? null : iv.getByteArray();
}
/**
* Implementation of ENCRYPTION-SALT attribute getter. Returns the default salt
* value (a random series of bytes) to use with the GENERATE-PBE-KEY function.
* The default value is the Unknown value (?), which indicates that no salt
* value is used to generate the password-based encryption key.
*
* @return The default salt value.
*/
public static raw getEncryptionSalt()
{
byte[] salt = work.get().encryptionSalt;
return salt == null ? raw.instantiateUnknownRaw() : new raw(salt.clone());
}
/**
* Implementation of SECURITY-POLICY:ENCRYPTION-SALT attribute setter. Sets the
* default salt value (a random series of bytes) to use with the
* GENERATE-PBE-KEY function. The default value is the Unknown value (?), which
* indicates that no salt value is used to generate the password-based
* encryption key.
*
* @param salt The default salt value.
*/
public static void setEncryptionSalt(raw salt)
{
work.get().encryptionSalt = salt.isUnknown() ? null : salt.getByteArray();
}
/**
* Implementation of PBE-KEY-ROUNDS attribute getter. Returns the number of hash
* algorithm iterations to perform in the GENERATE-PBE-KEY function to generate
* a password-based encryption key. The value must be a positive integer. The
* default value is 1000.
*
* @return The number of hash algorithm iterations.
*/
public static integer getPbeKeyRounds()
{
return new integer(work.get().pkeKeyRounds);
}
/**
* Implementation of SECURITY-POLICY:PBE-KEY-ROUNDS attribute setter. Sets the
* number of hash algorithm iterations to perform in the GENERATE-PBE-KEY
* function to generate a password-based encryption key. The value must be a
* positive integer. The default value is 1000.
*
* @param rounds The number of hash algorithm iterations.
*/
public static void setPbeKeyRounds(integer rounds)
{
if (rounds.isUnknown() || rounds.intValue() <= 0)
{
invalidValue(12222, "PBE-KEY-ROUNDS");
return;
}
work.get().pkeKeyRounds = rounds.intValue();
}
/**
* Implementation of PBE-HASH-ALGORITHM attribute getter. Returns the number of
* hash algorithm iterations to perform in the GENERATE-PBE-KEY function to
* generate a password-based encryption key. The value must be a positive
* integer. The default value is 1000.
*
* @return The number of hash algorithm iterations.
*/
public static character getPbeHashAlgorithm()
{
return new character(work.get().pbeHashAlgorithm);
}
/**
* Implementation of SECURITY-POLICY:PBE-HASH-ALGORITHM attribute setter. Sets
* the number of hash algorithm iterations to perform in the GENERATE-PBE-KEY
* function to generate a password-based encryption key. The value must be a
* positive integer. The default value is 1000.
*
* @param aname The number of hash algorithm iterations.
*/
public static void setPbeHashAlgorithm(character aname)
{
if (aname.isUnknown() || !DIGESTS.contains(StringUtils.upperCase(aname.value)))
{
invalidValue(12221, "SYMMETRIC-ENCRYPTION-ALGORITHM");
return;
}
work.get().pbeHashAlgorithm = aname.value;
}
/**
* Implementation of SECURITY-POLICY:GET-CLIENT method. Returns the handle to a
* copy of the sealed client-principal object that represents the user identity
* for the ABL session. If no identity has been established for the session
* using the SECURITY-POLICY:SET-CLIENT( ) method, this method returns the
* Unknown value (?).
*
* @return The handle to a copy of the sealed client-principal object.
*/
public static handle getClient()
{
return work.get().client;
}
/**
* Get domain access code by domain name.
*
* @param domainName domain name.
* @param error <code>Consumer</code> for an error code.
*
* @return optional domain code if found
*/
public static Optional<String> getDomainAccessCode(String domainName, Consumer<DomainStateError> error)
{
DomainRegistry registry = work.get().domainRegistry;
if (!registry.loadDomainsWasCalled && !registry.registerDomainWasCalled)
{
error.accept(DomainStateError.REGISTRY_IS_EMPTY);
return Optional.empty();
}
if (!registry.isLocked())
{
error.accept(DomainStateError.REGISTRY_IS_NOT_LOCKED);
return Optional.empty();
}
Domain domain = registry.get(domainName);
if (domain == null)
{
error.accept(DomainStateError.DOMAIN_NOT_EXISTS);
return Optional.empty();
}
if (!domain.isEnabled())
{
error.accept(DomainStateError.DOMAIN_DISABLED);
return Optional.empty();
}
return Optional.of(domain.getAccessCode());
}
/**
* Implementation of SECURITY-POLICY:SET-CLIENT method. Sets the identity, using
* an unsealed or a sealed client-principal object, for the current ABL session,
* and for all connected and unlocked OpenEdge database connections.
*
* @param h
* The handle to an unsealed or a sealed client-principal object.
*
* @return {@code true} if operation succeeded or {@code false} otherwise.
*/
public static logical setClient(handle h)
{
if (h == null || h.isUnknown())
{
ErrorManager.recordOrShowError(
15918,
"Required parameter for SET-CLIENT was passed the Unknown value",
true,
false,
false,
false
);
return logical.FALSE;
}
WorkArea wa = work.get();
DomainRegistry registry = wa.domainRegistry;
if (!registry.loadDomainsWasCalled && !registry.registerDomainWasCalled)
{
String msg = ConnectionManager.getConnectedDbCount() == 0 ?
"session registry database not connected" :
"the session registry is not loaded";
ErrorManager.recordOrShowError(new int[] {15945},
new String[] { "SECURITY-POLICY:SET-CLIENT failed because: " + msg },
false, false, false, false, false);
return logical.FALSE;
}
ClientPrincipalResource clientPrincipal = (ClientPrincipalResource) h.getResource();
LoginState loginState = LoginState.valueOf(clientPrincipal.getLoginState().toStringMessage());
Domain domain = registry.get(clientPrincipal.getDomainName().toStringMessage());
switch (loginState)
{
case INITIAL:
if (domain instanceof PersistentDomain)
{
if (!SecurityOps._USER_TABLE_AUTH_SYSTEM.equalsIgnoreCase(domain.getType().getName()))
{
ClientPrincipal.authenticationFailed(16392, "Session",
"Domain authentication action not supported ");
clientPrincipal.authenticationFailed("The Domain authentication action is not allowed");
return logical.FALSE;
}
return ((PersistentDomain)domain).validate(clientPrincipal);
}
if (registry.loadDomainsWasCalled && domain == null)
{
ClientPrincipal.authenticationFailed(16388, "Session",
"Cannot find Domain configuration ");
clientPrincipal.authenticationFailed("Cannot find the named PAM domain service");
return logical.FALSE;
}
authenticationNotSupported();
return logical.FALSE;
case EXPIRED:
if (registry.db == null)
{
authenticationNotSupported();
return logical.FALSE;
}
if (clientPrincipal.isSealed())
{
ClientPrincipal.validationFailed(16376, "Session", "User authentication expired");
return logical.FALSE;
}
// "princpal" - as in 4GL
ErrorManager.recordOrShowError(new int[] {15944},
new String[] {
"Failed Session client princpal authentication because: the " +
"Client-Principal is in an invalid state"
},
false, false, false, false, false);
return logical.FALSE;
case LOGIN:
if (!registry.isLocked())
{
ClientPrincipal.validationFailed(16366, "Session",
"Internal Authentication subsystem error [41]");
return logical.FALSE;
}
if (domain == null)
{
ClientPrincipal.validationFailed(16389, "Session", "Nonexistent Domain");
return logical.FALSE;
}
if (!clientPrincipal.validateDomainAccessCode(
"Session", domain.getAccessCode(), SealCallee.SET_CLIENT).booleanValue())
{
ClientPrincipal.validationFailed(16385, "Session", "The client-principal was corrupt");
ErrorManager.recordOrShowError(new int[] { 13691 },
new String[] { String.format("Failed to set %s user id.", "Session") }, false,
false, false, false, false);
return logical.FALSE;
}
ArrayList<String> unlockedDBs = ConnectionManager.getUnlocked();
if (false && (domain instanceof RuntimeDomain) && !unlockedDBs.isEmpty())
{
// TODO: if all DBs are locked, then don't fail if at least one is already auth with
// the same client-principal...
ClientPrincipal.validationFailed(16388, ConnectionManager.pdbName(1).toStringMessage(),
"Cannot find Domain configuration");
return logical.FALSE;
}
// TODO: process loaded domain
boolean ok = true;
for (int i = 0; i < unlockedDBs.size(); i++)
{
ok &= SecurityOps.setDbClient(h, unlockedDBs.get(i), SealCallee.SET_CLIENT).booleanValue();
}
wa.client = h;
return logical.of(ok);
case LOGOUT:
ClientPrincipal.validationFailed(16367, "Session", "Insufficient privileges");
return logical.FALSE;
case FAILED:
ClientPrincipal.validationFailed(16369, "Session", "Failed account authentication");
return logical.FALSE;
default: // TODO: handle other states
return logical.FALSE;
}
}
/**
* Implementation of SECURITY-POLICY:REGISTER-DOMAIN method. Registers a
* security domain in the ABL session domain registry. The AVM uses this
* registry to authenticate or validate (through a single sign-on (SSO)
* operation) the session identity represented by a client-principal object, as
* well as the connection identity for any OpenEdge database configured to use
* the session (application) registry.
*
* @param domainName
* A character expression that specifies the name of this security domain.
* @param accessCode
* A character expression that specifies the secret value to use when authenticating or validating
* a client-principal object that represents a user identity in this domain.
*
* @return {@code true} if operation succeeded or {@code false} otherwise.
*/
public static logical registerDomain(character domainName, character accessCode)
{
return registerDomain(domainName, accessCode, new character(), new character());
}
/**
* Implementation of SECURITY-POLICY:REGISTER-DOMAIN method. Registers a
* security domain in the ABL session domain registry. The AVM uses this
* registry to authenticate or validate (through a single sign-on (SSO)
* operation) the session identity represented by a client-principal object, as
* well as the connection identity for any OpenEdge database configured to use
* the session (application) registry.
*
* @param domainName
* A character expression that specifies the name of this security domain.
* @param accessCode
* A character expression that specifies the secret value to use when authenticating or validating
* a client-principal object that represents a user identity in this domain.
* @param domainDesc
* An optional character expression that specifies a description for this domain.
*
* @return {@code true} if operation succeeded or {@code false} otherwise.
*/
public static logical registerDomain(character domainName, character accessCode, character domainDesc)
{
return registerDomain(domainName, accessCode, domainDesc, new character());
}
/**
* Implementation of SECURITY-POLICY:REGISTER-DOMAIN method. Registers a
* security domain in the ABL session domain registry. The AVM uses this
* registry to authenticate or validate (through a single sign-on (SSO)
* operation) the session identity represented by a client-principal object, as
* well as the connection identity for any OpenEdge database configured to use
* the session (application) registry.
*
* @param domainName
* A character expression that specifies the name of this security domain.
* @param accessCode
* A character expression that specifies the secret value to use when authenticating or validating
* a client-principal object that represents a user identity in this domain.
* @param domainDesc
* An optional character expression that specifies a description for this domain.
* @param domainType
* An optional character expression that specifies an application-defined authentication system
* for user authentication and single-sign-on (SSO) operations.
*
* @return {@code true} if operation succeeded or {@code false} otherwise.
*/
public static logical registerDomain(character domainName,
character accessCode,
character domainDesc,
character domainType)
{
DomainRegistry registry = work.get().domainRegistry;
if (registry.isLocked())
{
ErrorManager.recordOrShowError(new int[] {14560},
new String[] {
"SECURITY-POLICY:REGISTER-DOMAIN failed because the registry is already locked"
},
false, false, false, false, false);
return logical.FALSE;
}
registry.registerDomainWasCalled = true;
DomainType dt = SecurityOps.addDomainType(
null,
domainType == null || domainType.isUnknown() ? null : domainType.toStringMessage(),
null, true
);
RuntimeDomain domain = new RuntimeDomain(
domainName == null || domainName.isUnknown() ? "" : domainName.toStringMessage(),
dt,
domainDesc == null || domainDesc.isUnknown() ? null : domainDesc.toStringMessage(),
accessCode == null || accessCode.isUnknown() ? null : accessCode.toStringMessage()
);
registry.add(domain);
return logical.TRUE;
}
/**
* Implementation of SECURITY-POLICY:LOCK-REGISTRATION method. Prevents the
* registration of additional domains in the ABL session domain registry for the
* remainder of an ABL session. You must call this method to use the domains you
* have registered in the session domain registry using the REGISTER-DOMAIN( )
* method.
*
* @return {@code true} if operation succeeded or {@code false} otherwise.
*/
public static logical lockRegistration()
{
DomainRegistry registry = work.get().domainRegistry;
boolean locked = registry.lock();
if (!locked)
{
ErrorManager.recordOrShowError(new int[] {14562},
new String[] {
"SECURITY-POLICY:LOCK-REGISTRATION failed because registry is already locked"
},
false, false, false, false, false);
}
return logical.of(locked);
}
/**
* Loads registered domains from the specified (and connected) OpenEdge RDBMS into the ABL
* session domain registry.
*
* @param num
* The sequence number of a connected database from which to load registered domains.
*
* @return {@code true} if operation succeeded or {@code false} otherwise.
*/
public static logical loadDomains(integer num)
{
DomainRegistry registry = work.get().domainRegistry;
if (registry.registerDomainWasCalled)
{
return loadDomainsFailed(14566,
"SECURITY-POLICY:REGISTER-DOMAIN has already been called");
}
registry.loadDomainsWasCalled = true;
if (num == null || num.isUnknown())
{
return invalidLoadDomainsParam();
}
int n = num.intValue();
int ndb = ConnectionManager.getConnectedDbCount();
if (n < 1 || n > ndb)
{
return invalidLoadDomainsParam();
}
String dbname = ConnectionManager.pdbName(ndb).toStringMessage();
Persistence persistence = ConnectionManager.getPersistence(dbname);
return loadDomains(dbname, persistence);
}
/**
* Loads registered domains from the specified (and connected) OpenEdge RDBMS into the ABL
* session domain registry.
*
* @param num
* The sequence number of a connected database from which to load registered domains
*
* @return {@code true} if operation succeeded or {@code false} otherwise.
*/
public static logical loadDomains(int num)
{
return loadDomains(new integer(num));
}
/**
* Loads registered domains from the specified (and connected) OpenEdge RDBMS into the ABL
* session domain registry.
*
* @param name
* The logical name or alias of a connected database from which to load registered domains.
*
* @return {@code true} if operation succeeded or {@code false} otherwise.
*/
public static logical loadDomains(character name)
{
DomainRegistry registry = work.get().domainRegistry;
if (registry.registerDomainWasCalled)
{
return loadDomainsFailed(14566,
"SECURITY-POLICY:REGISTER-DOMAIN has already been called");
}
registry.loadDomainsWasCalled = true;
if (name == null || name.isUnknown())
{
return invalidLoadDomainsParam();
}
String db = name.value;
Persistence persistence = ConnectionManager.getPersistence(db);
if (persistence == null)
{
return invalidLoadDomainsParam();
}
return loadDomains(db, persistence);
}
/**
* Loads registered domains from the specified (and connected) OpenEdge RDBMS into the ABL
* session domain registry.
*
* @param name
* The logical name or alias of a connected database from which to load registered domains.
*
* @return {@code true} if operation succeeded or {@code false} otherwise.
*/
public static logical loadDomains(String name)
{
return loadDomains(new character(name));
}
/**
* API needed to implement read-only attribute assignment (a 4GL "feature").
*
* @param attribute
* The attribute's name.
*
* @see handle#readOnlyError(handle, String)
*/
public static void readOnlyError(String attribute)
{
handle.readOnlyError(asHandle(), attribute);
}
/**
* API needed to implement read-only attribute assignment (a 4GL "feature").
*
* @param attribute
* The attribute's name.
* @param expr
* The value which is attempted to be assigned to the read-only attribute.
*
* @see handle#readOnlyError(handle, String, Object)
*/
public static void readOnlyError(String attribute, Object expr)
{
handle.readOnlyError(asHandle(), attribute);
}
/**
* Get this resource's ID.
*
* @return The resource's ID.
*/
public static Long id()
{
WorkArea wa = work.get();
return wa.id;
}
/**
* Implementation of SECURITY-POLICY:SYMMETRIC-ENCRYPTION-KEY attribute getter
* for internal use.
*
* @return the symmetric encryption key value
*/
static raw getSymmetricEncryptionKeyInternal()
{
byte[] key = work.get().encryptionKey;
return key == null ? raw.instantiateUnknownRaw() : new raw(key);
}
/**
* Report "authentication not supported" error.
*/
private static void authenticationNotSupported()
{
ErrorManager.recordOrShowError(new int[] {15945},
new String[] {
"SECURITY-POLICY:SET-CLIENT failed because: authentication not supported by" +
" application defined registry",
},
false, false, false, false, false);
}
/**
* Loads registered domains from the specified (and connected) OpenEdge RDBMS into the ABL
* session domain registry.
*
* @param dbName
* The database name.
* @param persistence
* The {@link Persistence} instance for a connected database from which to load registered
* domains.
*
* @return {@code true} if operation succeeded or {@code false} otherwise.
*/
private static logical loadDomains(String dbName, Persistence persistence)
{
DomainRegistry registry = work.get().domainRegistry;
Database defDb = persistence.getDatabase(Persistence.SHARED_CTX).getDefault();
if (registry.db != null && !defDb.equals(registry.db))
{
return invalidLoadDomainsParam();
}
SecurityOps.refreshUsersAndDomains(dbName, persistence);
registry.db = defDb;
registry.dbn = dbName;
return logical.TRUE;
}
/**
* Report LOAD-DOMAINS failure and return <code>logical.FALSE</code>.
*
* @param code
* The error code.
* @param reason
* A short message explaining the cause of failure.
*
* @return Always {@code false}
*/
private static logical loadDomainsFailed(int code, String reason)
{
ErrorManager.recordOrShowError(new int[] { code },
new String[] {
String.format("SECURITY-POLICY:LOAD-DOMAINS failed because %s", reason) },
false, false, false, false, false);
return logical.FALSE;
}
/**
* Report invalid parameter for LOAD-DOMAINS and return <code>logical.FALSE</code>.
*
* @return <code>false</code>
*/
private static logical invalidLoadDomainsParam()
{
ErrorManager.recordOrShowError(new int[] { 14564 },
new String[] { "Invalid parameter for SECURITY-POLICY:LOAD-DOMAINS" }, false, false,
false, false, false);
return logical.FALSE;
}
/**
* Report invalid attribute value error.
*
* @param code
* The error code.
* @param aname
* The attribute name.
*/
private static void invalidValue(int code, String aname)
{
int[] nums = { code, 3131 };
String[] texts = { String.format("Invalid %s value", aname),
String.format("Unable to set attribute %s in widget of type PSEUDO-WIDGET.", aname) };
ErrorManager.recordOrThrowError(nums, texts, false, false, false);
}
/**
* Stores global data relating to the state of the current context.
*/
private static class WorkArea
{
/** The resource's ID. */
private Long id = null;
/** SECURITY-POLICY:SYMMETRIC-ENCRYPTION-ALGORITHM attribute value. */
private String symmetricEncryptionAlgorithm = "AES_CBC_128";
/** SECURITY-POLICY:PBE-HASH-ALGORITHM attribute value. */
private String pbeHashAlgorithm = "SHA-1";
/** SECURITY-POLICY:PBE-KEY-ROUNDS attribute value. */
private int pkeKeyRounds = 1000;
/** SECURITY-POLICY:ENCRYPTION-SALT attribute value. */
private byte[] encryptionSalt = null;
/** SECURITY-POLICY:SYMMETRIC-ENCRYPTION-KEY attribute value. */
private byte[] encryptionKey = null;
/** SECURITY-POLICY:SYMMETRIC-ENCRYPTION-IV attribute value. */
private byte[] encryptionIV = null;
/** domain registry. */
private final DomainRegistry domainRegistry = new DomainRegistry();
/** client. */
private handle client = new handle();
}
/** Domain loaded from the connected permanent database. */
private static class PersistentDomain
extends Domain
{
/** The database name. */
private final String dbn;
/**
* Constructor.
*
* @param dbn
* The database name.
* @param d
* {@link Domain} instance.
*/
public PersistentDomain(String dbn, Domain d)
{
super(d);
this.dbn = dbn;
}
/**
* Validate CLINE-PRINCIPAL.
*
* @param clientPrincipal
* client principal to be validated.
*
* @return <code>true</code> when successful.
*/
public logical validate(ClientPrincipalResource clientPrincipal)
{
String password = getUserPwd(new QUserId(clientPrincipal));
return clientPrincipal.validatePassword("Session", null, password, SealCallee.SET_CLIENT);
}
/**
* Get user password.
*
* @param quid
* qualified user name.
*
* @return user password or <code> null</code> if no user found.
*/
public String getUserPwd(QUserId quid)
{
Optional<Map<QUserId, String>> users = SecurityOps.getUsers(dbn);
return users.map(m -> m.get(quid)).orElse(null);
}
}
/**
* Errors for {@code getDomainAccessCode()} method.
*/
public enum DomainStateError
{
/** Registry is empty */
REGISTRY_IS_EMPTY(14541, "registry is empty"),
/** Registry is not locked */
REGISTRY_IS_NOT_LOCKED(14541, "registry is not locked"),
/** Domain not found */
DOMAIN_NOT_EXISTS(14541, "domain does not exist in the registry"),
/** Domain disabled */
DOMAIN_DISABLED(16391, " - Domian is disabled");
/** The error code. */
public final int code;
/** The error message */
public final String message;
/**
* The constructor for this immutable object.
*
* @param code
* The error code.
* @param message
* The error message.
*/
DomainStateError(int code, String message)
{
this.code = code;
this.message = message;
}
}
/** Runtime security domain.*/
public static class RuntimeDomain
extends Domain
{
/**
* Create a new domain and initialize it with the given state.
*
* @param name
* The domain's name.
* @param type
* The domain's type.
* @param description
* The domain's description.
* @param accessCode
* The domain's access code.
*/
public RuntimeDomain(String name, DomainType type, String description, String accessCode)
{
super(name, type, description, accessCode, null);
}
}
/** Domain registry. */
private static class DomainRegistry
{
/** Flag indicating that domain is locked. */
private final AtomicBoolean locked = new AtomicBoolean(false);
/** Flag indicating that LOAD-DOMAINS was called. */
private boolean loadDomainsWasCalled = false;
/** Flag indicating that REGISTER-DOMIAN was called. */
private boolean registerDomainWasCalled = false;
/** Registered domains by name. */
private final Map<String, RuntimeDomain> domains = new ConcurrentHashMap<>();
/** Database used in the LOAD-DOMAINS call. */
private Database db;
/** Name of the database used in the LOAD-DOMAINS call. */
private String dbn;
/**
* Lock registry.
*
* @return <code>false</code> if the registry is already locked
*/
private boolean lock()
{
return locked.compareAndSet(false, true);
}
/**
* Add domain to the registry.
*
* @param domain
* domain to be added.
*/
public void add(RuntimeDomain domain)
{
domains.put(domain.getName(), domain);
}
/**
* Retrieve domain by name.
*
* @param name
* domain name.
*
* @return registered domain with the specified name or <code>null</code> if not found.
*/
public Domain get(String name)
{
Domain d = SecurityOps.findDomain(dbn, name);
if (d != null)
{
return new PersistentDomain(dbn, d);
}
return domains.get(name);
}
/**
* Check if registry is locked.
*
* @return <code>true</code> if the registry is locked.
*/
private boolean isLocked()
{
return locked.get() || loadDomainsWasCalled;
}
}
}