Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.akeyless.api.AkeylessApiImpl Maven / Gradle / Ivy
package com.akeyless.api;
import com.akeyless.api.auth.AuthApi;
import com.akeyless.api.auth.AuthApiImpl;
import com.akeyless.api.exceptions.*;
import com.akeyless.api.kfm.KfmApiImpl;
import com.akeyless.api.uam.UamApi;
import com.akeyless.api.uam.UamApiImpl;
import com.akeyless.api.utils.APIErrorResponse;
import com.akeyless.api.utils.AkeylessItemType;
import com.akeyless.api.utils.ApiUtils;
import com.akeyless.api.utils.DeriveKeyResult;
import com.akeyless.auth.swagger.model.CredentialsReplyObj;
import com.akeyless.auth.swagger.model.SetUAMPolicyCredsParams;
import com.akeyless.auth.swagger.model.SystemUserCredentialsReplyObj;
import com.akeyless.config.AkeylessUserConfiguration;
import com.akeyless.crypto.rsa.RSAUtils;
import com.akeyless.crypto.rsa.RsaMpcFragment;
import com.akeyless.crypto.rsa.RsaMpcSign;
import com.akeyless.crypto.rsa.RsaMpcUtils;
import com.akeyless.crypto.utils.CryptoAlgorithm;
import com.akeyless.crypto.xor.XorUtils;
import com.akeyless.exceptions.AkeylessRuntimeException;
import com.akeyless.kfm.swagger.model.DerivedFragmentReplyObj;
import com.akeyless.kfm.swagger.model.RSAFragmentDecryptReplyObj;
import com.akeyless.uam.swagger.model.*;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.BadPaddingException;
import java.io.IOException;
import java.math.BigInteger;
import java.security.*;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.*;
public class AkeylessApiImpl implements AkeylessApi {
private AkeylessUserConfiguration config;
private UamApi uamApi;
private AuthApi authApi;
public AkeylessApiImpl(AkeylessUserConfiguration config) throws ApiCommunicationException {
this.config = config;
this.uamApi = new UamApiImpl(config.getProtocol(), config.getUamServerDNS());
this.authApi = new AuthApiImpl(config.getProtocol(), this.uamApi.getStatus().getAuthDns());
}
public UamApi getUamApi() {
return uamApi;
}
private class ConcurrentRes {
String serverDNS;
int fragSerialNum;
ApiCommunicationException comException = null;
ApiBaseException apiException = null;
}
private class DerivedFragmentConcurrentRes extends ConcurrentRes {
private DerivedFragmentReplyObj derivedFragment = null;
}
private class RSAFragmentDecryptConcurrentRes extends ConcurrentRes {
private RSAFragmentDecryptReplyObj rsaDecryptFragment = null;
}
private class UploadRSAFragmentConcurrentRes extends ConcurrentRes {
private com.akeyless.kfm.swagger.model.UploadRSAFragmentReplyObj testSignature = null;
}
@Override
public SystemUserCredentialsReplyObj authenticateUAMApiKeyPolicy(
String policyId, Long timestamp, String nonce, String signature, String clientIp,Integer credsExpiry)
throws ApiCommunicationException, AuthenticationFailedException, InvalidParamException, NotFoundException, MissingRequiredParamException {
return this.authApi.authenticateUAMApiKeyPolicy(policyId, timestamp, nonce, signature, clientIp, credsExpiry);
}
@Override
public GetAccountDetailsReplyObj getAccountDetails(String akeylessUAMUserCreds) throws ApiCommunicationException,
CredentialsNotFoundException, InvalidCredentialsException, NotFoundException {
return this.uamApi.getAccountDetails(akeylessUAMUserCreds);
}
@Override
public DerivationCredsReplyObj getItemDerivationCreds(
String akeylessUAMUserCreds, String itemName, Integer itemVersion, String restrictedDerivationData, Long credsExpiry)
throws ApiCommunicationException, InvalidParamException, MissingRequiredParamException,
NotFoundException, CredentialsNotFoundException, InvalidCredentialsException {
return this.uamApi.getItemDerivationCreds(akeylessUAMUserCreds, itemName, itemVersion, restrictedDerivationData, credsExpiry);
}
@Override
public RSADecryptCredsReplyObj getRSAKeyDecryptCreds(String akeylessUAMUserCreds,
String itemName,
Integer itemVersion,
String restrictedCipher,
Long credsExpiry
) throws ApiCommunicationException, InvalidParamException, MissingRequiredParamException,
NotFoundException, CredentialsNotFoundException, InvalidCredentialsException{
return this.uamApi.getRSAKeyDecryptCreds(akeylessUAMUserCreds, itemName, itemVersion, restrictedCipher, credsExpiry);
}
@Override
public DeriveKeyResult deriveKey(final DerivationCredsReplyObj derivationCreds,
final String akeylessKFMUserCreds,
final ArrayList derivationsData,
final boolean doubleDerivation)
throws ApiCommunicationException, MissingRequiredParamException, InvalidParamException,
CredentialsNotFoundException, InvalidCredentialsException, NotFoundException, AkeylessRuntimeException {
int numOfFragments = derivationCreds.getKfMsHostsDNSMap().size();
if(derivationsData == null || derivationsData.size() != numOfFragments) {
throw new AkeylessRuntimeException("Invalid number of derivations data");
}
ExecutorService executor = Executors.newFixedThreadPool(numOfFragments);
CompletionService completionService =
new ExecutorCompletionService<>(executor);
for(int i = 0; i < numOfFragments; i++) {
final String serverDNS = derivationCreds.getKfMsHostsDNSMap().get(Integer.toString(i));
final int fragSerialNum = i;
final String operationCreds = derivationCreds.getCredential();
final String dd = new String(Base64.encodeBase64(derivationsData.get(i)));
completionService.submit(new Callable() {
public DerivedFragmentConcurrentRes call() {
KfmApiImpl kfmApi = new KfmApiImpl(config.getProtocol(), serverDNS);
DerivedFragmentConcurrentRes derivedFragmentRes = new DerivedFragmentConcurrentRes();
derivedFragmentRes.serverDNS = serverDNS;
derivedFragmentRes.fragSerialNum = fragSerialNum;
try {
derivedFragmentRes.derivedFragment =
kfmApi.deriveFragment(akeylessKFMUserCreds, operationCreds, dd, doubleDerivation);
} catch (ApiCommunicationException e) {
derivedFragmentRes.comException = e;
} catch (ApiBaseException e) {
derivedFragmentRes.apiException = e;
}
return derivedFragmentRes;
}
});
}
ArrayList fragments = new ArrayList<>(Collections.nCopies(numOfFragments, new byte[0]));
ArrayList derivationsDataRes = new ArrayList<>(Collections.nCopies(numOfFragments, new byte[0]));
int received = 0;
while(received < numOfFragments) {
Future resultFuture = null;
try {
resultFuture = completionService.take();
DerivedFragmentConcurrentRes result = resultFuture.get(20, TimeUnit.SECONDS);
if(result.derivedFragment == null) {
executor.shutdownNow();
}
if(result.comException != null){
throw new ApiCommunicationException(result.comException);
}
handleConcurrentExceptions(result);
if(result.derivedFragment == null) {
throw new AkeylessRuntimeException("unexpected result. received null DerivedFragmentReplyObj");
}
fragments.set(result.fragSerialNum, Base64.decodeBase64(result.derivedFragment.getDerivedFragment()));
derivationsDataRes.set(result.fragSerialNum, Base64.decodeBase64(result.derivedFragment.getDerivationData()));
received ++;
} catch (ExecutionException | InterruptedException e) {
executor.shutdownNow();
throw new AkeylessRuntimeException(e);
} catch (TimeoutException e) {
resultFuture.cancel(true);
executor.shutdownNow();
throw new ApiCommunicationException(e);
}
}
return new DeriveKeyResult(XorUtils.xorFragments(fragments), derivationsDataRes);
}
@Override
public List rsaFragmentDecrypt(final RSADecryptCredsReplyObj rsaDecryptCreds,
final String akeylessKFMUserCreds,
final String cipher
) throws ApiCommunicationException, MissingRequiredParamException, InvalidParamException,
CredentialsNotFoundException, InvalidCredentialsException, NotFoundException{
int numOfFragments = rsaDecryptCreds.getKfMsHostsDNSMap().size();
ExecutorService executor = Executors.newFixedThreadPool(numOfFragments);
CompletionService completionService =
new ExecutorCompletionService<>(executor);
for(int i = 0; i < numOfFragments; i++) {
final String serverDNS = rsaDecryptCreds.getKfMsHostsDNSMap().get(Integer.toString(i));
final int fragSerialNum = i;
final String operationCreds = rsaDecryptCreds.getCredential();
completionService.submit(new Callable() {
public RSAFragmentDecryptConcurrentRes call() {
KfmApiImpl kfmApi = new KfmApiImpl(config.getProtocol(), serverDNS);
RSAFragmentDecryptConcurrentRes rsaDecryptFragmentRes = new RSAFragmentDecryptConcurrentRes();
rsaDecryptFragmentRes.serverDNS = serverDNS;
rsaDecryptFragmentRes.fragSerialNum = fragSerialNum;
try {
rsaDecryptFragmentRes.rsaDecryptFragment =
kfmApi.rsaFragmentDecrypt(akeylessKFMUserCreds, operationCreds, cipher);
} catch (ApiCommunicationException e) {
rsaDecryptFragmentRes.comException = e;
} catch (ApiBaseException e) {
rsaDecryptFragmentRes.apiException = e;
}
return rsaDecryptFragmentRes;
}
});
}
List decryptResults = new ArrayList<>(Collections.nCopies(numOfFragments, BigInteger.ZERO));
int received = 0;
while(received < numOfFragments) {
Future resultFuture = null;
try {
resultFuture = completionService.take();
RSAFragmentDecryptConcurrentRes result = resultFuture.get(20, TimeUnit.SECONDS);
if(result.rsaDecryptFragment == null) {
executor.shutdownNow();
}
if(result.comException != null){
throw new ApiCommunicationException(result.comException);
}
handleConcurrentExceptions(result);
if(result.rsaDecryptFragment == null) {
throw new AkeylessRuntimeException("unexpected result. received null RSAFragmentDecryptReplyObj");
}
decryptResults.set(result.fragSerialNum, new BigInteger(1,
Base64.decodeBase64(result.rsaDecryptFragment.getDecryptRes())));
received ++;
} catch (ExecutionException | InterruptedException e) {
executor.shutdownNow();
throw new AkeylessRuntimeException(e);
} catch (TimeoutException e) {
resultFuture.cancel(true);
executor.shutdownNow();
throw new ApiCommunicationException(e);
}
}
return decryptResults;
}
@Override
public void createAESKeyItem(String akeylessUAMUserCreds,
String itemName,
AkeylessItemType itemType,
Long splitLevel,
String userMetadata
) throws ApiCommunicationException, InvalidParamException, MissingRequiredParamException, CredentialsNotFoundException,
InvalidCredentialsException, AlreadyExistsException, UnauthorizedUserException {
if(!itemType.isAESKey()){
throw new AkeylessRuntimeException("invalid algorithm");
}
this.uamApi.createItem(akeylessUAMUserCreds, itemName, itemType.getName(), splitLevel, userMetadata, 0L, "");
}
@Override
public void createRSAKeyItem(final String akeylessUAMUserCreds,
final String akeylessKFMUserCreds,
String itemName,
AkeylessItemType itemType,
Long splitLevel,
String userMetadata
) throws ApiCommunicationException, InvalidParamException, MissingRequiredParamException, CredentialsNotFoundException,
InvalidCredentialsException, AlreadyExistsException, UnauthorizedUserException, NoSuchAlgorithmException,
SignatureException, InvalidKeyException {
if(!itemType.isRSAKey()){
throw new AkeylessRuntimeException("invalid algorithm");
}
CryptoAlgorithm alg = CryptoAlgorithm.getAlgByName(itemType.getName());
KeyPair pair = RSAUtils.generateKeyPair(alg.getKeyLenBytes());
PrivateKey prvKey = pair.getPrivate();
RSAPrivateCrtKey rsaPrv = null;
if (prvKey instanceof RSAPrivateCrtKey) {
rsaPrv = (RSAPrivateCrtKey) prvKey;
}
uploadRSAKeyItem(akeylessUAMUserCreds, akeylessKFMUserCreds, rsaPrv, pair.getPublic(), itemName,
itemType, splitLevel, userMetadata);
}
private void uploadRSAKeyItem(final String akeylessUAMUserCreds,
final String akeylessKFMUserCreds,
RSAPrivateCrtKey prvKey,
PublicKey pubKey,
String itemName,
AkeylessItemType itemType,
Long splitLevel,
String userMetadata
) throws ApiCommunicationException, InvalidParamException, MissingRequiredParamException,
CredentialsNotFoundException, InvalidCredentialsException, AlreadyExistsException,
UnauthorizedUserException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
List fragments = RsaMpcUtils.splitPrivateKey(prvKey, splitLevel.intValue());
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(pubKey.getEncoded());
String base64PubKey = new String(Base64.encodeBase64(publicKeySpec.getEncoded()));
UploadRSAKeyCredsReplyObj uploadCreds = this.uamApi.getUploadRSAKeyCreds(akeylessUAMUserCreds,
itemName, itemType.getName(), base64PubKey, splitLevel, userMetadata, 0L);
int numOfFragments = uploadCreds.getKfMsHostsDNSMap().size();
if(splitLevel != numOfFragments) {
throw new AkeylessRuntimeException("Invalid number of RSA key fragments");
}
ExecutorService executor = Executors.newFixedThreadPool(numOfFragments);
CompletionService completionService =
new ExecutorCompletionService<>(executor);
String testMsg = "Akeyless RSA fragment test operation";
byte[] testMsgBytes = null;
try {
testMsgBytes = RsaMpcSign.preSignPKCS1v15(testMsg.getBytes(), prvKey.getModulus());
} catch (IOException | BadPaddingException e) {
throw new AkeylessRuntimeException("Failed to create the test message");
}
final String testMsgEncoded = new String(Base64.encodeBase64(testMsgBytes));
for(int i = 0; i < numOfFragments; i++) {
final String serverDNS = uploadCreds.getKfMsHostsDNSMap().get(Integer.toString(i));
final int fragSerialNum = i;
final String operationCreds = uploadCreds.getCredential();
final String fragmentVal = new String(Base64.encodeBase64(fragments.get(i).getFD().toByteArray()));
completionService.submit(new Callable() {
public UploadRSAFragmentConcurrentRes call() {
KfmApiImpl kfmApi = new KfmApiImpl(config.getProtocol(), serverDNS);
UploadRSAFragmentConcurrentRes uploadRSAFragmentRes = new UploadRSAFragmentConcurrentRes();
uploadRSAFragmentRes.serverDNS = serverDNS;
uploadRSAFragmentRes.fragSerialNum = fragSerialNum;
try {
uploadRSAFragmentRes.testSignature =
kfmApi.uploadRSAFragment(akeylessKFMUserCreds, operationCreds, fragmentVal, testMsgEncoded);
} catch (ApiCommunicationException e) {
uploadRSAFragmentRes.comException = e;
} catch (ApiBaseException e) {
uploadRSAFragmentRes.apiException = e;
}
return uploadRSAFragmentRes;
}
});
}
ArrayList testSignatures = new ArrayList<>(Collections.nCopies(numOfFragments, new byte[0]));
int received = 0;
while(received < numOfFragments) {
Future resultFuture = null;
try {
resultFuture = completionService.take();
UploadRSAFragmentConcurrentRes result = resultFuture.get(20, TimeUnit.SECONDS);
if(result.testSignature == null) {
executor.shutdownNow();
}
if(result.comException != null){
throw new ApiCommunicationException(result.comException);
}
handleConcurrentExceptions(result);
testSignatures.set(result.fragSerialNum, Base64.decodeBase64(result.testSignature.getTestSignature()));
received ++;
} catch (ExecutionException | InterruptedException | NotFoundException e) {
executor.shutdownNow();
throw new AkeylessRuntimeException(e);
} catch (TimeoutException e) {
resultFuture.cancel(true);
executor.shutdownNow();
throw new ApiCommunicationException(e);
}
}
ApiUtils.validateFragmentedSignature(testMsg.getBytes(), testSignatures, prvKey);
this.uamApi.createItem(akeylessUAMUserCreds, itemName, itemType.getName(), splitLevel,
userMetadata, 0L, uploadCreds.getCredential());
}
@Override
public GetItemReplyObj getItemWithAttributes(String akeylessUAMUserCreds,
String itemName,
Integer itemVersion
) throws ApiCommunicationException, InvalidParamException, MissingRequiredParamException,
NotFoundException, CredentialsNotFoundException, InvalidCredentialsException {
return this.uamApi.getItem(akeylessUAMUserCreds, itemName, itemVersion);
}
@Override
public GetItemReplyObj getItem(String akeylessUAMUserCreds, String itemName)
throws ApiCommunicationException, InvalidParamException, MissingRequiredParamException,
NotFoundException, CredentialsNotFoundException, InvalidCredentialsException {
return this.uamApi.getItem(akeylessUAMUserCreds, itemName, 0);
}
@Override
public GetUserItemsReplyObj getUserItems(String akeylessUAMUserCreds)
throws ApiCommunicationException, InvalidParamException, MissingRequiredParamException,
NotFoundException, CredentialsNotFoundException, InvalidCredentialsException {
return this.uamApi.getUserItems(akeylessUAMUserCreds);
}
@Override
public void updateItem(String akeylessUAMUserCreds,
String newItemName,
String itemName,
String userMetadata
) throws ApiCommunicationException, MissingRequiredParamException, InvalidParamException,
CredentialsNotFoundException, InvalidCredentialsException, AlreadyExistsException {
uamApi.updateItem(akeylessUAMUserCreds, newItemName, itemName, userMetadata);
}
@Override
public void deleteItem(String akeylessUAMUserCreds, String itemName) throws ApiCommunicationException,
MissingRequiredParamException, InvalidParamException, CredentialsNotFoundException,
InvalidCredentialsException, NotFoundException {
uamApi.deleteItem(akeylessUAMUserCreds, itemName);
}
@Override
public CreateUserReplyObj createUser(String akeylessUAMUserCreds,
String akeylessSetUserAccessPolicyCreds,
String userName
) throws ApiCommunicationException, MissingRequiredParamException, InvalidParamException,
CredentialsNotFoundException, InvalidCredentialsException, AlreadyExistsException {
return uamApi.createUser(akeylessUAMUserCreds, akeylessSetUserAccessPolicyCreds, userName);
}
@Override
public GetUserReplyObj getUser(String akeylessUAMUserCreds, String userName) throws ApiCommunicationException,
MissingRequiredParamException, InvalidParamException, CredentialsNotFoundException,
InvalidCredentialsException, NotFoundException {
return uamApi.getUser(akeylessUAMUserCreds, userName);
}
@Override
public GetAccountUsersReplyObj getAccountUsers(String akeylessUAMUserCreds) throws ApiCommunicationException,
MissingRequiredParamException, InvalidParamException, CredentialsNotFoundException,
InvalidCredentialsException, NotFoundException {
return uamApi.getAccountUsers(akeylessUAMUserCreds);
}
@Override
public void updateUser(String akeylessUAMUserCreds,
String akeylessSetUserAccessPolicyCreds,
String newUserName,
String userName
) throws ApiCommunicationException, MissingRequiredParamException, InvalidParamException,
CredentialsNotFoundException, InvalidCredentialsException, AlreadyExistsException, NotFoundException {
uamApi.updateUser(akeylessUAMUserCreds, akeylessSetUserAccessPolicyCreds, newUserName, userName);
}
@Override
public void deleteUser(String akeylessUAMUserCreds, String userName) throws ApiCommunicationException,
MissingRequiredParamException, InvalidParamException, CredentialsNotFoundException,
InvalidCredentialsException, NotFoundException {
uamApi.deleteUser(akeylessUAMUserCreds, userName);
}
@Override
public CredentialsReplyObj setUamPolicyCreds(String akeylessAuthCreds,
SetUAMPolicyCredsParams policyParams
) throws ApiCommunicationException, MissingRequiredParamException, InvalidParamException,
CredentialsNotFoundException, InvalidCredentialsException {
return authApi.setUamPolicyCreds(akeylessAuthCreds, policyParams);
}
@Override
public void createRole(String akeylessUAMUserCreds,
String roleName,
String roleAction,
String comment
) throws ApiCommunicationException, MissingRequiredParamException, InvalidParamException,
CredentialsNotFoundException, InvalidCredentialsException, AlreadyExistsException {
uamApi.createRole(akeylessUAMUserCreds, roleName, roleAction, comment);
}
@Override
public GetRoleReplyObj getRole(String akeylessUAMUserCreds,
String roleName
) throws ApiCommunicationException, MissingRequiredParamException, InvalidParamException,
CredentialsNotFoundException, InvalidCredentialsException, NotFoundException {
return uamApi.getRole(akeylessUAMUserCreds, roleName);
}
@Override
public GetAccountRolesReplyObj getAccountRoles(String akeylessUAMUserCreds) throws ApiCommunicationException,
MissingRequiredParamException, InvalidParamException, CredentialsNotFoundException,
InvalidCredentialsException, NotFoundException {
return uamApi.getAccountRoles(akeylessUAMUserCreds);
}
@Override
public void updateRole(String akeylessUAMUserCreds,
String newRoleName,
String roleName,
String roleAction,
String comment
) throws ApiCommunicationException, MissingRequiredParamException, InvalidParamException,
CredentialsNotFoundException, InvalidCredentialsException, NotFoundException, AlreadyExistsException {
uamApi.updateRole(akeylessUAMUserCreds, newRoleName, roleName, roleAction, comment);
}
@Override
public void deleteRole(String akeylessUAMUserCreds,
String roleName
) throws ApiCommunicationException, MissingRequiredParamException, InvalidParamException,
CredentialsNotFoundException, InvalidCredentialsException, NotFoundException {
uamApi.deleteRole(akeylessUAMUserCreds, roleName);
}
@Override
public void createRoleItemAssoc(String akeylessUAMUserCreds,
String roleName,
String itemName
) throws ApiCommunicationException, MissingRequiredParamException, InvalidParamException,
CredentialsNotFoundException, InvalidCredentialsException, NotFoundException {
uamApi.createRoleItemAssoc(akeylessUAMUserCreds, roleName, itemName);
}
@Override
public void createRoleUserAssoc(String akeylessUAMUserCreds,
String roleName,
String userName
) throws ApiCommunicationException, MissingRequiredParamException, InvalidParamException,
CredentialsNotFoundException, InvalidCredentialsException, NotFoundException {
uamApi.createRoleUserAssoc(akeylessUAMUserCreds, roleName, userName);
}
@Override
public void deleteRoleItemAssoc(String akeylessUAMUserCreds,
String roleName,
String itemName
) throws ApiCommunicationException, MissingRequiredParamException, InvalidParamException,
CredentialsNotFoundException, InvalidCredentialsException, NotFoundException {
uamApi.deleteRoleItemAssoc(akeylessUAMUserCreds, roleName, itemName);
}
@Override
public void deleteRoleUserAssoc(String akeylessUAMUserCreds,
String roleName,
String userName
) throws ApiCommunicationException, MissingRequiredParamException, InvalidParamException,
CredentialsNotFoundException, InvalidCredentialsException, NotFoundException {
uamApi.deleteRoleUserAssoc(akeylessUAMUserCreds, roleName, userName);
}
private void handleConcurrentExceptions(ConcurrentRes result) throws MissingRequiredParamException, InvalidParamException,
CredentialsNotFoundException, InvalidCredentialsException, NotFoundException {
if(result.apiException != null){
int statusCode = result.apiException.getResponseCode();
APIErrorResponse errorResponse = result.apiException.getErrorResponse();
if(result.apiException instanceof MissingRequiredParamException) {
throw new MissingRequiredParamException(statusCode, errorResponse);
}
if(result.apiException instanceof InvalidParamException) {
throw new InvalidParamException(statusCode, errorResponse);
}
if(result.apiException instanceof CredentialsNotFoundException) {
throw new CredentialsNotFoundException(statusCode, errorResponse);
}
if(result.apiException instanceof InvalidCredentialsException) {
throw new InvalidCredentialsException(statusCode, errorResponse);
}
if(result.apiException instanceof NotFoundException) {
throw new NotFoundException(statusCode, errorResponse);
}
}
}
}