
com.azure.identity.implementation.IntelliJCacheAccessor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of azure-identity Show documentation
Show all versions of azure-identity Show documentation
This module contains client library for Microsoft Azure Identity.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.identity.implementation;
import com.azure.core.util.CoreUtils;
import com.azure.core.util.logging.ClientLogger;
import com.azure.identity.CredentialUnavailableException;
import com.azure.identity.AzureAuthorityHosts;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.microsoft.aad.msal4jextensions.persistence.mac.KeyChainAccessor;
import com.sun.jna.Platform;
import com.sun.jna.platform.win32.Crypt32Util;
import org.linguafranca.pwdb.Database;
import org.linguafranca.pwdb.Entry;
import org.linguafranca.pwdb.kdbx.KdbxCreds;
import org.linguafranca.pwdb.kdbx.simple.SimpleDatabase;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.file.Paths;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Base64;
/**
* This class accesses IntelliJ Azure Tools credentials cache via JNA.
*/
public class IntelliJCacheAccessor {
private final ClientLogger logger = new ClientLogger(IntelliJCacheAccessor.class);
private String keePassDatabasePath;
private static final String INTELLIJ_CREDENTIAL_NOT_AVAILABLE_ERROR = "IntelliJ Authentication not available."
+ " Please log in with Azure Tools for IntelliJ plugin in the IDE.";
private static final byte[] CRYPTO_KEY = new byte[] {0x50, 0x72, 0x6f, 0x78, 0x79, 0x20, 0x43, 0x6f, 0x6e, 0x66,
0x69, 0x67, 0x20, 0x53, 0x65, 0x63};
/**
* Creates an instance of {@link IntelliJCacheAccessor}
*
* @param keePassDatabasePath the KeePass database path.
*/
public IntelliJCacheAccessor(String keePassDatabasePath) {
this.keePassDatabasePath = keePassDatabasePath;
}
private List getAzureToolsforIntelliJPluginConfigPaths() {
return Arrays.asList(Paths.get(System.getProperty("user.home"), "AzureToolsForIntelliJ").toString(),
Paths.get(System.getProperty("user.home"), ".AzureToolsForIntelliJ").toString());
}
/**
* Get the Device Code credential details of Azure Tools plugin in the IntelliJ IDE.
*
* @return the {@link JsonNode} holding the authentication details.
* @throws IOException
*/
public JsonNode getDeviceCodeCredentials() throws IOException {
if (Platform.isMac()) {
KeyChainAccessor accessor = new KeyChainAccessor(null, "ADAuthManager",
"cachedAuthResult");
String jsonCred = new String(accessor.read(), Charset.forName("UTF-8"));
ObjectMapper mapper = new ObjectMapper();
return mapper.readTree(jsonCred);
} else if (Platform.isLinux()) {
LinuxKeyRingAccessor accessor = new LinuxKeyRingAccessor(
"com.intellij.credentialStore.Credential",
"service", "ADAuthManager",
"account", "cachedAuthResult");
String jsonCred = new String(accessor.read(), Charset.forName("UTF-8"));
if (jsonCred.startsWith("cachedAuthResult@")) {
jsonCred = jsonCred.replaceFirst("cachedAuthResult@", "");
}
ObjectMapper mapper = new ObjectMapper();
return mapper.readTree(jsonCred);
} else if (Platform.isWindows()) {
return getCredentialFromKdbx();
} else {
throw logger.logExceptionAsError(new RuntimeException(String.format("OS %s Platform not supported.",
Platform.getOSType())));
}
}
/**
* Get the Service Principal credential details of Azure Tools plugin in the IntelliJ IDE.
*
* @param credFilePath the file path holding authentication details
* @return the {@link HashMap} holding auth details.
* @throws IOException if an error is countered while reading the credential file.
*/
public Map getIntellijServicePrincipalDetails(String credFilePath) throws IOException {
BufferedReader reader = null;
HashMap servicePrincipalDetails = new HashMap<>(8);
try {
reader = new BufferedReader(new FileReader(credFilePath));
String line = reader.readLine();
while (line != null) {
String[] split = line.split("=");
split[1] = split[1].replace("\\", "");
servicePrincipalDetails.put(split[0], split[1]);
// read next line
line = reader.readLine();
}
} finally {
if (reader != null) {
reader.close();
}
}
return servicePrincipalDetails;
}
@SuppressWarnings({"rawtypes", "unchecked"})
private JsonNode getCredentialFromKdbx() throws IOException {
if (CoreUtils.isNullOrEmpty(keePassDatabasePath)) {
throw logger.logExceptionAsError(
new CredentialUnavailableException("The KeePass database path is either empty or not configured."
+ " Please configure it on the builder. It is required to use "
+ "IntelliJ credential on the windows platform."));
}
String extractedpwd = getKdbxPassword();
SecretKeySpec key = new SecretKeySpec(CRYPTO_KEY, "AES");
String password = "";
byte[] dataToDecrypt = Crypt32Util.cryptUnprotectData(Base64.getDecoder().decode(extractedpwd));
ByteBuffer decryptBuffer = ByteBuffer.wrap(dataToDecrypt);
Cipher cipher = null;
try {
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
int ivLen = decryptBuffer.getInt();
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(dataToDecrypt, decryptBuffer.position(), ivLen));
int dataOffset = decryptBuffer.position() + ivLen;
byte[] decrypted = cipher.doFinal(dataToDecrypt, dataOffset, dataToDecrypt.length - dataOffset);
password = new String(decrypted, Charset.forName("UTF-8"));
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
| InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
throw logger.logExceptionAsError(new RuntimeException("Unable to access cache.", e));
}
try {
KdbxCreds creds = new KdbxCreds(password.getBytes(Charset.forName("UTF-8")));
InputStream inputStream = new FileInputStream(new File(keePassDatabasePath));
Database database = SimpleDatabase.load(creds, inputStream);
List entries = database.findEntries("ADAuthManager");
if (entries.size() == 0) {
throw logger.logExceptionAsError(new CredentialUnavailableException("No credentials found in the cache."
+ " Please login with IntelliJ Azure Tools plugin in the IDE."));
}
ObjectMapper mapper = new ObjectMapper();
return mapper.readTree(entries.get(0).getPassword());
} catch (Exception e) {
throw logger.logExceptionAsError(new RuntimeException("Failed to read KeePass database.", e));
}
}
private String getKdbxPassword() throws IOException {
String passwordFilePath = new File(keePassDatabasePath).getParent() + File.separator + "c.pwd";
String extractedpwd = "";
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(passwordFilePath));
String line = reader.readLine();
while (line != null) {
if (line.contains("value")) {
String[] tokens = line.split(" ");
if (tokens.length == 3) {
extractedpwd = tokens[2];
break;
} else {
throw logger.logExceptionAsError(new RuntimeException("Password not found in the file."));
}
}
line = reader.readLine();
}
reader.close();
} finally {
if (reader != null) {
reader.close();
}
}
return extractedpwd;
}
/**
* Get the auth host of the specified {@code azureEnvironment}.
* @param azureEnvironment the specified Azure Environment
* @return the auth host.
*/
public String getAzureAuthHost(String azureEnvironment) {
switch (azureEnvironment) {
case "GLOBAL":
return AzureAuthorityHosts.AZURE_PUBLIC_CLOUD;
case "CHINA":
return AzureAuthorityHosts.AZURE_CHINA;
case "GERMAN":
return AzureAuthorityHosts.AZURE_GERMANY;
case "US_GOVERNMENT":
return AzureAuthorityHosts.AZURE_GOVERNMENT;
default:
return AzureAuthorityHosts.AZURE_PUBLIC_CLOUD;
}
}
/**
* Get the current authentication method details of Azure Tools plugin in IntelliJ IDE.
*
* @return the {@link IntelliJAuthMethodDetails}
* @throws IOException if an error is encountered while reading the auth details file.
*/
public IntelliJAuthMethodDetails getAuthDetailsIfAvailable() throws IOException {
File authFile = null;
for (String metadataPath : getAzureToolsforIntelliJPluginConfigPaths()) {
String authMethodDetailsPath =
Paths.get(metadataPath, "AuthMethodDetails.json").toString();
authFile = new File(authMethodDetailsPath);
if (authFile.exists()) {
break;
}
}
if (authFile == null || !authFile.exists()) {
throw logger.logExceptionAsError(
new CredentialUnavailableException(INTELLIJ_CREDENTIAL_NOT_AVAILABLE_ERROR));
}
ObjectMapper objectMapper = new ObjectMapper();
IntelliJAuthMethodDetails authMethodDetails = objectMapper
.readValue(authFile, IntelliJAuthMethodDetails.class);
String authType = authMethodDetails.getAuthMethod();
if (CoreUtils.isNullOrEmpty(authType)) {
throw logger.logExceptionAsError(
new CredentialUnavailableException(INTELLIJ_CREDENTIAL_NOT_AVAILABLE_ERROR));
}
if (authType.equalsIgnoreCase("SP")) {
if (CoreUtils.isNullOrEmpty(authMethodDetails.getCredFilePath())) {
throw logger.logExceptionAsError(
new CredentialUnavailableException(INTELLIJ_CREDENTIAL_NOT_AVAILABLE_ERROR));
}
} else if (authType.equalsIgnoreCase("DC")) {
if (CoreUtils.isNullOrEmpty(authMethodDetails.getAccountEmail())) {
throw logger.logExceptionAsError(
new CredentialUnavailableException(INTELLIJ_CREDENTIAL_NOT_AVAILABLE_ERROR));
}
}
return authMethodDetails;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy