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

oracle.kv.impl.security.metadata.SecurityMetadata Maven / Gradle / Ivy

Go to download

NoSQL Database Server - supplies build and runtime support for the server (store) side of the Oracle NoSQL Database.

There is a newer version: 18.3.10
Show newest version
/*-
 * Copyright (C) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This file was distributed by Oracle as part of a version of Oracle NoSQL
 * Database made available at:
 *
 * http://www.oracle.com/technetwork/database/database-technologies/nosqldb/downloads/index.html
 *
 * Please see the LICENSE file included in the top-level directory of the
 * appropriate version of Oracle NoSQL Database for a copy of the license and
 * additional information.
 */

package oracle.kv.impl.security.metadata;

import java.io.IOException;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import oracle.kv.impl.metadata.Metadata;
import oracle.kv.impl.security.KVStoreUserPrincipal;
import oracle.kv.impl.security.RoleInstance;
import oracle.kv.impl.security.RoleInstance.RoleDescription;
import oracle.kv.impl.security.metadata.KVStoreUser.UserDescription;
import oracle.kv.impl.security.metadata.SecurityMDChange.SecurityMDChangeType;
import oracle.kv.impl.topo.StorageNodeId;
import oracle.kv.impl.util.SerializationUtil;

/**
 * Class for security metadata. The class contains all elements of security
 * metadata, e.g., user data, role data, audit policy and other information
 * needed in future. Currently, the KVStoreUser and RoleInstance information
 * are included.
 * 

* The security metadata can be fully updated as whole. And it also supports * delta-update mechanism. A history change list is used to keep all changes * happened to the security metadata copy. All the changes can also be applied * to another copy to make it updated to the targeted status. *

* The SecurityMetadata is NOT thread safe. The concurrency protocol for it is * to always give out copies. Updates will be made on a new copy, and the old * one will be replaced as a whole. This can save the complex concurrency * control and corruption recovery mechanism. *

* On Admin side, the SecurityMetadata copy will be fetched from EntityStore * on each get. And the update will be performed in plans, which will be * executed serially. On the RepNode side, the access to SecurityMetadata is * coordinated by SecurityMetadataManager, in which concurrent updates and read * will be protected by "synchronized". */ public class SecurityMetadata implements Metadata, Serializable { private static final long serialVersionUID = 1L; /** * The types of elements stored in security metadata. */ public static enum SecurityElementType { KVSTOREUSER, KVSTOREROLE, KRBPRINCIPAL } private final String id; private final KVStoreUserMap kvstoreUserMap = new KVStoreUserMap(this); private KVStoreRoleMap kvstoreRoleMap = new KVStoreRoleMap(this); private KerberosInstanceMap krbInstanceMap = new KerberosInstanceMap(this); /* TODO: need to include AuditPolicy in future */ /* Used to identify which store the metadata belongs to */ private final String kvstoreName; private final LinkedList changeList = new LinkedList(); private int sequenceNumber = Metadata.EMPTY_SEQUENCE_NUMBER; /* Descriptions of system build-in assignable roles */ private static final TreeMap builtInRoleInfoMap = new TreeMap(); static { builtInRoleInfoMap.put(RoleInstance.DBADMIN_NAME, RoleInstance.DBADMIN.getDescription()); builtInRoleInfoMap.put(RoleInstance.READONLY_NAME, RoleInstance.READONLY.getDescription()); builtInRoleInfoMap.put(RoleInstance.READWRITE_NAME, RoleInstance.READWRITE.getDescription()); builtInRoleInfoMap.put(RoleInstance.SYSADMIN_NAME, RoleInstance.SYSADMIN.getDescription()); builtInRoleInfoMap.put(RoleInstance.WRITEONLY_NAME, RoleInstance.WRITEONLY.getDescription()); builtInRoleInfoMap.put(RoleInstance.PUBLIC_NAME, RoleInstance.PUBLIC.getDescription()); } /** * Create a SecurityMetadata instance with KVStore name. The ID is * "SecurityMD-" + currentTimeMillis by default. */ public SecurityMetadata(final String storeName) { this(storeName, "SecurityMD-" + System.currentTimeMillis()); } /** * Create a SecurityMetadata instance with specified KVStore name and ID. */ public SecurityMetadata(final String storeName, final String id) { this.kvstoreName = storeName; this.id = id; } public String getKVStoreName() { return kvstoreName; } public String getId() { return id; } /** * Returns a KVStore user with the specified name. * * @param name user name * @return the user, null if no user with the specified name is found. */ public KVStoreUser getUser(final String name) { if ((name == null) || name.isEmpty()) { return null; } final Collection users = kvstoreUserMap.getAll(); for (final KVStoreUser user : users) { if (name.equals(user.getName())) { return user; } } return null; } /** * Returns a KVStore user-defined role with the specified name. * * @param name role name * @return the role, null if no role with the specified name is found. */ public RoleInstance getRole(final String name) { if ((name == null) || name.isEmpty()) { return null; } final Collection roles = kvstoreRoleMap.getAll(); for (final RoleInstance role : roles) { if (name.equalsIgnoreCase(role.name())) { return role; } } return null; } /** * Returns Kerberos principal instance name of given storage node. * * @param snId storage node Id * @return principal instance name, null if no one with the specified * storage node id is found. */ public KerberosInstance getKrbInstance(final StorageNodeId snId) { if ((snId == null) || snId.getStorageNodeId() == 0) { return null; } final Collection instances = krbInstanceMap.getAll(); for (final KerberosInstance instance : instances) { if (snId.equals(instance.getStorageNodeId())) { return instance; } } return null; } /** * Returns a KVStore user with the specified ID. * * @param uid user id * @return the user, null if no user with the specified ID is found. */ public KVStoreUser getUserById(final String uid) { return kvstoreUserMap.get(uid); } /** * Returns a KVStore user-defined role with the specified ID. * * @param rid role id * @return the role, null if no role with the specified ID is found. */ public RoleInstance getRoleById(final String rid) { return kvstoreRoleMap.get(rid); } /** * Returns a Kerberos principal instance name with the specified ID. * * @param kid kerberos instance name id * @return instance name, null if no one with the specified ID is found. */ public KerberosInstance getKrbInstanceById(final String kid) { return krbInstanceMap.get(kid); } /** * Returns all users. */ public Collection getAllUsers() { return kvstoreUserMap.getAll(); } /** * Returns all Kerberos principal instance names. */ public Collection getAllKrbInstanceNames() { return krbInstanceMap.getAll(); } /** * Returns all user-defined roles. */ public Collection getAllRoles() { return kvstoreRoleMap.getAll(); } /** * Returns the internal KVStoreUser map. * * @return KVStoreUserMap */ public KVStoreUserMap getKVStoreUserMap() { return kvstoreUserMap; } /** * Returns the internal RoleInstance map. * * @return RoleInstanceMap */ public KVStoreRoleMap getRoleInstanceMap() { return kvstoreRoleMap; } /** * Returns the internal Kerberos principal instance map. * * @return KerberosInstanceMap */ public KerberosInstanceMap getKerberosInstanceMap() { return krbInstanceMap; } /** * Adds a new user into the map. * * @param user user instance * @return the newly added user */ public KVStoreUser addUser(final KVStoreUser user) { return kvstoreUserMap.add(user); } /** * Adds a new role into the map. * * @param role user-defined role * @return the newly added role */ public RoleInstance addRole(final RoleInstance role) { return kvstoreRoleMap.add(role); } /** * Adds service principal instance name of given storage node into the map. * * @param instanceName Kerberos principal instance name * @param snId storage node Id * @return KerberosInstance of this storage node if adding succeed or null * if given storage node id is invalid or its instance name exists * in metadata already. */ public KerberosInstance addKerberosInstanceName(final String instanceName, final StorageNodeId snId) { return krbInstanceMap.addInstanceName(instanceName, snId); } /** * Removes a user with the specified ID. If the user does not exist, an * IllegalArgumentException will be thrown. * * @param uid user ID * @return the removed user if successful * @throws IllegalArgumentException if no user with specified ID is found */ public KVStoreUser removeUser(final String uid) { return kvstoreUserMap.remove(uid); } /** * Removes a role with the specified ID. If the role does not exist, an * IllegalArgumentException will be thrown. * * @param rid role ID * @return the removed role if successful * @throws IllegalArgumentException if no role with specified ID is found */ public RoleInstance removeRole(final String rid) { return kvstoreRoleMap.remove(rid); } /** * Removes service principal instance name from the map with the specified * StorageNodeId. * * @param snId storage node Id * @return the removed instance or null if not found */ public KerberosInstance removeKrbInstanceName(final StorageNodeId snId) { return krbInstanceMap.removeInstanceName(snId); } /** * Updates an existing user of the specified ID with a new user data. If * the user does not exist, an IllegalArgumentException will be thrown. * * @param uid user ID * @param user user * @return the new user if successful * @throws IllegalArgumentException if no user with specified ID is found */ public KVStoreUser updateUser(final String uid, final KVStoreUser user) { return kvstoreUserMap.update(uid, user); } /** * Updates an existing role of the specified ID with a new role data. If * the role does not exist, an IllegalArgumentException will be thrown. * * @param rid role ID * @param role user-defined role * @return the new role if successful * @throws IllegalArgumentException if no role with specified ID is found */ public RoleInstance updateRole(final String rid, final RoleInstance role) { return kvstoreRoleMap.update(rid, role); } /** * Verifies if the plain password works for the given user. * * @param userName user name * @param password the plain password to verify * @return true iff. the specified password works */ public boolean verifyUserPassword(final String userName, final char[] password) { final KVStoreUser user = getUser(userName); if (user == null) { return false; } return user.verifyPassword(password); } /** * Returns the brief and detailed description of all users for showing. The * descriptions in the returned Map are ordered by user name. * * @return a SortedMap of user descriptions which are sorted by user name */ public SortedMap getUsersDescription() { final Collection users = getAllUsers(); /* Use TreeMap to order the description by users' names */ final TreeMap userInfoMap = new TreeMap(); for (final KVStoreUser user : users) { userInfoMap.put(user.getName(), user.getDescription()); } return userInfoMap; } /** * Returns the brief and detailed description of all roles for showing. The * descriptions in the returned Map are ordered by role name. * * @return a SortedMap of role descriptions which are sorted by role name */ public SortedMap getRolesDescription() { final TreeMap roleDescMap = new TreeMap(builtInRoleInfoMap); for (final RoleInstance role : kvstoreRoleMap.getAll()) { roleDescMap.put(role.name(), role.getDescription()); } return roleDescMap; } /** * Returns the description of the current session user for showing. If * could not identify current user, null will be returned. * * @return Current user's description, null will be returned if current * user could not be identified or does not exist in metadata. */ public Map getCurrentUserDescription() { final KVStoreUserPrincipal currentUserPrinc = KVStoreUserPrincipal.getCurrentUser(); if (currentUserPrinc == null) { return null; } final KVStoreUser currentUser = kvstoreUserMap.get(currentUserPrinc.getUserId()); if (currentUser == null) { return null; } final Map userInfoMap = new HashMap(); userInfoMap.put(currentUser.getName(), currentUser.getDescription()); return userInfoMap; } /** * Check if given user is the last enabled user having sysadmin role * in the system. */ public boolean isLastSysadminUser(String userName) { final KVStoreUser existUser = getUser(userName); if (existUser != null && existUser.isAdmin()) { for (final KVStoreUser user : getAllUsers()) { if (!user.getName().equals(userName) && user.isEnabled() && user.isAdmin()) { return false; } } return true; } return false; } /** * Applies a series of change on the security metadata. The changes applied * should be in increasing sequence number order. *

* Note that the object may be corrupted because of unexpected exceptions * while applying changes. For simplicity we do not implement recovering * codes for such failure. So a safe way to call the operation is to get * a deep copy of the object first, and then apply the changes on the copy. * If the applying failed, the corrupted copy could be simply discarded. * * @param changes list of SecurityMDChange * @return true if any update is made */ public boolean apply(final List changes) { if (changes == null || changes.isEmpty()) { return false; } if (changes.get(0).getSeqNum() > (getSequenceNumber() + 1)) { throw new IllegalStateException( "Unexpected gap in security metadata sequence. Current " + "sequence = " + getSequenceNumber() + ", first change =" + changes.get(0).getSeqNum()); } int changedCount = 0; for (final SecurityMDChange change : changes) { /* Skip the change already existed */ if (change.getSeqNum() <= getSequenceNumber()) { continue; } if (change.getElementType() == SecurityElementType.KVSTOREUSER) { kvstoreUserMap.apply(change); changedCount++; } else if (change.getElementType() == SecurityElementType.KVSTOREROLE) { kvstoreRoleMap.apply(change); changedCount++; } else if (change.getElementType() == SecurityElementType.KRBPRINCIPAL) { krbInstanceMap.apply(change); changedCount++; } else { throw new IllegalArgumentException( "Unknown security element type: " + change.getElementType()); } } return changedCount > 0; } @Override public MetadataType getType() { return MetadataType.SECURITY; } /** * Returns the current change sequence number associated with security * metadata. If no change has been logged, the * {@code Metadata.EMPTY_SEQUENCE_NUMBER} will be return. */ @Override public int getSequenceNumber() { return sequenceNumber; } /** * Returns a list of security metadata changes starting with * startSeqNum. The list of changes will be wrapped into the * SecurityMetadataInfo object and will be used in the metadata framework. * * @param startSeqNum the inclusive start of the sequence of * changes to be returned * * @return the list of changes starting with startSeqNum and ending with * getSequenceNumber(). Return null if startSequenceNumber > * getSequenceNumber() or if the security metadata does not contain changes * at startSeqNum because they have been discarded. */ @Override public SecurityMetadataInfo getChangeInfo(int startSeqNum) { return new SecurityMetadataInfo(this, getChanges(startSeqNum)); } @Override public SecurityMetadata pruneChanges(final int minRetainSeqNum, final int maxChange) { final int firstChangeSeqNum = getFirstChangeSeqNum(); if (firstChangeSeqNum < 0) { /* No change to prune */ return this; } final int newStartSeqNum = Math.min(getSequenceNumber() - maxChange + 1, minRetainSeqNum); if (newStartSeqNum <= firstChangeSeqNum) { /* Nothing to prune. */ return this; } final Iterator itr = changeList.iterator(); while (itr.hasNext() && (itr.next().getSeqNum() < newStartSeqNum)) { itr.remove(); } return this; } /** * Gets the first sequence number of the changes stored in the tracker. * * @return the first sequence number of the changes, or -1 if no change * exists yet. */ public int getFirstChangeSeqNum() { return changeList.isEmpty() ? -1 : changeList.getFirst().getSeqNum(); } /** * Obtain a list of all changes that have a seqNum of startSeqNum or higher. * Returns null if a change with seqNum == startSeqNum is not present in * our change list. * * @param startSeqNum starting sequence number * @return a list containing all qualified changes, or null if a change * with seqNum == startSeqNum is not present. */ public List getChanges(final int startSeqNum) { /* * Return null if there's no change currently, or the starting seqNum * is out of the boundary of stored changes. */ if (changeList.isEmpty()) { return null; } if (startSeqNum < getFirstChangeSeqNum() || startSeqNum > changeList.getLast().getSeqNum()) { return null; } final List retChanges = new LinkedList(); for (final SecurityMDChange change : changeList) { if (change.getSeqNum() >= startSeqNum) { retChanges.add(change.clone()); } } return retChanges; } /** * Get all changes. */ public List getChanges() { return getChanges((changeList.size() == 0) ? 0 : changeList.getFirst().seqNum); } /** * Get latest change. */ public SecurityMDChange getLatestChange() { return changeList.getLast(); } /** * Create and return a deep copy of this SecurityMetadata object. * * @return the new SecurityMetadata instance */ public SecurityMetadata getCopy() { final byte[] mdBytes = SerializationUtil.getBytes(this); return SerializationUtil.getObject(mdBytes, this.getClass()); } @Override public String toString() { return String.format("SecurityMetadata id=%s seq# %d", id, sequenceNumber); } /** * Bump the sequence number, and log the change into the list. */ public void logChange(final SecurityMDChange change) { sequenceNumber++; change.setSeqNum(sequenceNumber); changeList.add(change); } private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); /* * Initialize user-defined role map if read the security metadata * object created in previous version. */ if (kvstoreRoleMap == null) { kvstoreRoleMap = new KVStoreRoleMap(this); } /* * Initialize Kerberos service principal map if read the security * metadata object created in previous version. */ if (krbInstanceMap == null) { krbInstanceMap = new KerberosInstanceMap(this); } } public static Map getBuiltInRoleInfo() { return Collections.unmodifiableMap(builtInRoleInfoMap); } /* * For testing */ public void setRoleMapId(int id) { this.kvstoreRoleMap.setId(id); } /** * Map for storing elements of KVStoreUser. */ public static class KVStoreUserMap extends ElementMap { private static final long serialVersionUID = 1L; public KVStoreUserMap(final SecurityMetadata securityMD) { super(securityMD); } @Override String nextId() { /* A user id is a digital number with prefix of "u". */ return "u" + (++idSequence); } @Override SecurityElementType getElementType() { return SecurityElementType.KVSTOREUSER; } } /** * Map for storing elements of RoleInstance. */ public static class KVStoreRoleMap extends ElementMap { private static final long serialVersionUID = 1L; private int currentId; public KVStoreRoleMap(final SecurityMetadata securityMD) { super(securityMD); } @Override String nextId() { /* A role id is a digital number with prefix of "r". */ return "r" + getNextId(); } /* * For testing */ void setId(int id) { idSequence = id; } private int getNextId() { if (idSequence < Integer.MAX_VALUE) { currentId = idSequence++; return idSequence; } for (currentId = 1; currentId < idSequence; currentId++) { if (this.get("r" + currentId) == null) { return currentId; } } throw new IllegalStateException("Could not add role, " + "the number of roles exceeds the limit"); } @Override SecurityElementType getElementType() { return SecurityElementType.KVSTOREROLE; } } public static class KerberosInstanceMap extends ElementMap { private static final long serialVersionUID = 1L; public KerberosInstanceMap(final SecurityMetadata securityMD) { super(securityMD); } /** * Add principal instance name for given storage node id. * * @param instanceName principal instance name * @param snId storage node id * @return KerberosInstance if adding succeed or null if given storage * node id is invalid * @throws IllegalStateException if an instance with the same name * already exist in the Kerberos principal map. */ public KerberosInstance addInstanceName(final String instanceName, final StorageNodeId snId) throws IllegalStateException { if (snId.getStorageNodeId() == 0) { return null; } for (KerberosInstance instance : getAll()) { if (instance.getStorageNodeId().equals(snId)) { return null; } } return add(new KerberosInstance(instanceName, snId)); } /** * Remove storage node principal instance name from security metadata. * * @param snId * @return KerberosInstance of this storage node if removing succeed or * null if it does not exists. */ public KerberosInstance removeInstanceName(final StorageNodeId snId) { for (KerberosInstance instance : getAll()) { if (instance.getStorageNodeId().equals(snId)) { return remove(instance.getElementId()); } } return null; } @Override SecurityElementType getElementType() { return SecurityElementType.KRBPRINCIPAL; } @Override String nextId() { /* A Kerberos instance id is a digital number with prefix of "k". */ return "k" + (++idSequence); } } /** * Abstract class for composed element of security metadata. */ public abstract static class SecurityElement implements Serializable, Cloneable { private static final long serialVersionUID = 1L; /* * The unique element id. It's null if the element has not been * "added" to the ElementMap. */ private String elementId; /** * Ctor with un-initialized elementId. The initialization of the * elementId will be deferred to, for example, when the element is * added in the storage container. */ protected SecurityElement() { } protected SecurityElement(final SecurityElement other) { this.elementId = other.elementId; } public void setElementId(final String eId) { this.elementId = eId; } public String getElementId() { return elementId; } @Override public boolean equals(Object obj) { if (!(obj instanceof SecurityElement)) { return false; } if (this == obj) { return true; } final SecurityElement other = (SecurityElement) obj; return (elementId == other.elementId || (elementId != null && elementId.equals(other.elementId))); } /** * Derived classes are required to calculate the hash code using their * own properties. */ @Override public abstract int hashCode(); @Override public abstract SecurityElement clone(); public abstract SecurityElementType getElementType(); } /** * Class holding a collection of SecurityElement. Use Map as the collection * to build a mapping between the element Ids and the elements. *

* Note that any operation modifying the elements may encounter unexpected * exceptions, and thus lead to corrupted data. The caller should handle * such cases. */ protected abstract static class ElementMap implements Serializable { private static final long serialVersionUID = 1L; private final SecurityMetadata securityMD; private final HashMap elementMap = new HashMap(); int idSequence; public ElementMap(final SecurityMetadata securityMD) { super(); this.securityMD = securityMD; } T get(final String elementId) { return elementMap.get(elementId); } Collection getAll() { return elementMap.values(); } /** * Add a new element into the ElementMap. A new internal ElementID will * be generated and assigned to the new element. * * @param element the new element * @return the new element * @throws IllegalStateException if an existing element is found with * the same ID */ T add(final T element) { return add(nextId(), element); } /** * Add a new element with its ElementID into the ElementMap. If the add * operation fails, i.e., an existing element with the same ID is found, * an IllegalStateException will be thrown. The caller should catch and * handle the exception. * * @param elementId ID of the element * @return the new element * @throws IllegalStateException if an existing element is found with * the same ID */ T add(final String elementId, final T element) { element.setElementId(elementId); final T prev = elementMap.put(elementId, element); securityMD.logChange(new SecurityMDChange.Add(element)); if (prev != null) { throw new IllegalStateException("Element " + prev + " was been overwritten by " + element); } return element; } T update(final String elementId, final T element) { if (elementMap.get(elementId) == null) { throw new IllegalArgumentException( "Element " + elementId + " absent from security metadata."); } element.setElementId(elementId); final T prev = elementMap.put(elementId, element); assert prev != null; securityMD.logChange(new SecurityMDChange.Update(element)); return element; } T remove(final String elementId) { final T prev = elementMap.remove(elementId); if (prev == null) { throw new IllegalArgumentException("Element " + elementId + "was not found."); } securityMD.logChange( new SecurityMDChange.Remove(elementId, getElementType(), prev)); return prev; } /** * Apply a change to the current ElementMap object * * @param change the change of element */ @SuppressWarnings("unchecked") void apply(final SecurityMDChange change) { final SecurityMDChangeType changeType = change.getChangeType(); final String changeElementId = change.getElementId(); switch (changeType) { case REMOVE: remove(changeElementId); break; case ADD: final String newId = nextId(); if (!newId.equals(changeElementId)) { throw new IllegalStateException( "Element sequence out of sync; expected: " + newId + " replayId: " + changeElementId); } add(newId, (T) change.getElement()); break; case UPDATE: update(changeElementId, (T) change.getElement()); break; default: throw new IllegalStateException( "Unknown change type: " + changeType); } /* * SecurityMetadata should have the same sequence with the change * after applying it. */ if (securityMD.getSequenceNumber() != change.getSeqNum()) { throw new IllegalStateException( "Mismatched security metadata change sequence: log# " + change.getSeqNum() + ", replay# " + securityMD.getSequenceNumber()); } } @Override public int hashCode() { final int prime = 31; final int result = 17 * prime + elementMap.hashCode(); return result * prime + idSequence; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } final ElementMap other = (ElementMap) obj; return (idSequence == other.idSequence) && elementMap.equals(other.elementMap); } abstract String nextId(); abstract SecurityElementType getElementType(); } /** * Kerberos principal instance name of storage node. */ public static class KerberosInstance extends SecurityElement { private static final long serialVersionUID = 1L; private final String instanceName; private final StorageNodeId snId; public KerberosInstance(String instanceName, StorageNodeId snId) { this.instanceName = instanceName; this.snId = snId; } protected KerberosInstance(KerberosInstance other) { super(other); this.snId = other.snId; this.instanceName = other.instanceName; } public StorageNodeId getStorageNodeId() { return this.snId; } public String getInstanceName() { return this.instanceName; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } if (!super.equals(obj)) { return false; } final KerberosInstance other = (KerberosInstance) obj; return snId.equals(other.snId); } @Override public int hashCode() { return snId.hashCode(); } @Override public SecurityElement clone() { return new KerberosInstance(this); } @Override public SecurityElementType getElementType() { return SecurityElementType.KRBPRINCIPAL; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy