Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AAD integration auth using DLL and ADAL4J #603

Merged
merged 15 commits into from
Jan 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>adal4j</artifactId>
<version>1.3.0</version>
<version>1.4.0</version>
<optional>true</optional>
</dependency>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ final class AuthenticationJNI extends SSPIAuthentication {
static int GetMaxSSPIBlobSize() {
return sspiBlobMaxlen;
}

static boolean isDllLoaded() {
return enabled;
}

static {
UnsatisfiedLinkError temp = null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package com.microsoft.sqlserver.jdbc;

import java.io.IOException;
import java.net.MalformedURLException;
import java.text.MessageFormat;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.logging.Level;

import javax.security.auth.kerberos.KerberosPrincipal;

import com.microsoft.aad.adal4j.AuthenticationContext;
import com.microsoft.aad.adal4j.AuthenticationException;
Expand All @@ -15,10 +19,13 @@

class SQLServerADAL4JUtils {

static final private java.util.logging.Logger adal4jLogger = java.util.logging.Logger
.getLogger("com.microsoft.sqlserver.jdbc.internals.SQLServerADAL4JUtils");

static SqlFedAuthToken getSqlFedAuthToken(SqlFedAuthInfo fedAuthInfo,
String user,
String password,
String authenticationString) throws SQLServerException {
String user,
String password,
String authenticationString) throws SQLServerException {
ExecutorService executorService = Executors.newFixedThreadPool(1);
try {
AuthenticationContext context = new AuthenticationContext(fedAuthInfo.stsurl, false, executorService);
Expand All @@ -42,7 +49,8 @@ static SqlFedAuthToken getSqlFedAuthToken(SqlFedAuthInfo fedAuthInfo,
String correctedErrorMessage = e.getCause().getMessage().replaceAll("\\\\r\\\\n", "\r\n");
AuthenticationException correctedAuthenticationException = new AuthenticationException(correctedErrorMessage);

// SQLServerException is caused by ExecutionException, which is caused by AuthenticationException
// SQLServerException is caused by ExecutionException, which is caused by
// AuthenticationException
// to match the exception tree before error message correction
ExecutionException correctedExecutionException = new ExecutionException(correctedAuthenticationException);

Expand All @@ -52,4 +60,57 @@ static SqlFedAuthToken getSqlFedAuthToken(SqlFedAuthInfo fedAuthInfo,
executorService.shutdown();
}
}

static SqlFedAuthToken getSqlFedAuthTokenIntegrated(SqlFedAuthInfo fedAuthInfo,
String authenticationString) throws SQLServerException {
ExecutorService executorService = Executors.newFixedThreadPool(1);

try {
// principal name does not matter, what matters is the realm name
// it gets the username in principal_name@realm_name format
KerberosPrincipal kerberosPrincipal = new KerberosPrincipal("username");
String username = kerberosPrincipal.getName();

if (adal4jLogger.isLoggable(Level.FINE)) {
adal4jLogger.fine(adal4jLogger.toString() + " realm name is:" + kerberosPrincipal.getRealm());
}

AuthenticationContext context = new AuthenticationContext(fedAuthInfo.stsurl, false, executorService);
Future<AuthenticationResult> future = context.acquireToken(fedAuthInfo.spn, ActiveDirectoryAuthentication.JDBC_FEDAUTH_CLIENT_ID,
username, null, null);

AuthenticationResult authenticationResult = future.get();
SqlFedAuthToken fedAuthToken = new SqlFedAuthToken(authenticationResult.getAccessToken(), authenticationResult.getExpiresOnDate());

return fedAuthToken;
}
catch (InterruptedException | IOException e) {
throw new SQLServerException(e.getMessage(), e);
}
catch (ExecutionException e) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_ADALExecution"));
Object[] msgArgs = {"", authenticationString};

if (null == e.getCause() || null == e.getCause().getMessage()) {
// the case when Future's outcome has no AuthenticationResult but exception
throw new SQLServerException(form.format(msgArgs), null);
}
else {
// the cause error message uses \\n\\r which does not give correct format
// change it to \r\n to provide correct format
String correctedErrorMessage = e.getCause().getMessage().replaceAll("\\\\r\\\\n", "\r\n");
AuthenticationException correctedAuthenticationException = new AuthenticationException(correctedErrorMessage);

// SQLServerException is caused by ExecutionException, which is caused by
// AuthenticationException
// to match the exception tree before error message correction
ExecutionException correctedExecutionException = new ExecutionException(correctedAuthenticationException);

throw new SQLServerException(form.format(msgArgs), null, 0, correctedExecutionException);
}
}
finally {
executorService.shutdown();
}
}
}
123 changes: 62 additions & 61 deletions src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -1539,11 +1539,6 @@ Connection connectInternal(Properties propsIn,
throw new SQLServerException(SQLServerException.getErrString("R_AccessTokenWithUserPassword"), null);
}

if ((!System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("windows"))
&& (authenticationString.equalsIgnoreCase(SqlAuthentication.ActiveDirectoryIntegrated.toString()))) {
throw new SQLServerException(SQLServerException.getErrString("R_AADIntegratedOnNonWindows"), null);
}

// Turn off TNIR for FedAuth if user does not set TNIR explicitly
if (!userSetTNIR) {
if ((!authenticationString.equalsIgnoreCase(SqlAuthentication.NotSpecified.toString())) || (null != accessTokenInByte)) {
Expand Down Expand Up @@ -3855,88 +3850,94 @@ private SqlFedAuthToken getFedAuthToken(SqlFedAuthInfo fedAuthInfo) throws SQLSe
// fedAuthInfo should not be null.
assert null != fedAuthInfo;

// No:of milliseconds to sleep for the inital back off.
int sleepInterval = 100;

// No:of attempts, for tracing purposes, if we underwent retries.
int numberOfAttempts = 0;

String user = activeConnectionProperties.getProperty(SQLServerDriverStringProperty.USER.toString());
String password = activeConnectionProperties.getProperty(SQLServerDriverStringProperty.PASSWORD.toString());

// No:of milliseconds to sleep for the inital back off.
int sleepInterval = 100;

while (true) {
numberOfAttempts++;

if (authenticationString.trim().equalsIgnoreCase(SqlAuthentication.ActiveDirectoryPassword.toString())) {
fedAuthToken = SQLServerADAL4JUtils.getSqlFedAuthToken(fedAuthInfo, user, password, authenticationString);

// Break out of the retry loop in successful case.
break;
}
else if (authenticationString.trim().equalsIgnoreCase(SqlAuthentication.ActiveDirectoryIntegrated.toString())) {
try {
long expirationFileTime = 0;
FedAuthDllInfo dllInfo = AuthenticationJNI.getAccessTokenForWindowsIntegrated(fedAuthInfo.stsurl, fedAuthInfo.spn,
clientConnectionId.toString(), ActiveDirectoryAuthentication.JDBC_FEDAUTH_CLIENT_ID, expirationFileTime);

// If operating system is windows and sqljdbc_auth is loaded then choose the DLL authentication.
if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("windows") && AuthenticationJNI.isDllLoaded()) {
try {
long expirationFileTime = 0;
FedAuthDllInfo dllInfo = AuthenticationJNI.getAccessTokenForWindowsIntegrated(fedAuthInfo.stsurl, fedAuthInfo.spn,
clientConnectionId.toString(), ActiveDirectoryAuthentication.JDBC_FEDAUTH_CLIENT_ID, expirationFileTime);

// AccessToken should not be null.
assert null != dllInfo.accessTokenBytes;
// AccessToken should not be null.
assert null != dllInfo.accessTokenBytes;

byte[] accessTokenFromDLL = dllInfo.accessTokenBytes;
byte[] accessTokenFromDLL = dllInfo.accessTokenBytes;

String accessToken = new String(accessTokenFromDLL, UTF_16LE);
String accessToken = new String(accessTokenFromDLL, UTF_16LE);

fedAuthToken = new SqlFedAuthToken(accessToken, dllInfo.expiresIn);
fedAuthToken = new SqlFedAuthToken(accessToken, dllInfo.expiresIn);

// Break out of the retry loop in successful case.
break;
}
catch (DLLException adalException) {

// the sqljdbc_auth.dll return -1 for errorCategory, if unable to load the adalsql.dll
int errorCategory = adalException.GetCategory();
if (-1 == errorCategory) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_UnableLoadADALSqlDll"));
Object[] msgArgs = {Integer.toHexString(adalException.GetState())};
throw new SQLServerException(form.format(msgArgs), null);
// Break out of the retry loop in successful case.
break;
}
catch (DLLException adalException) {

// the sqljdbc_auth.dll return -1 for errorCategory, if unable to load the adalsql.dll
int errorCategory = adalException.GetCategory();
if (-1 == errorCategory) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_UnableLoadADALSqlDll"));
Object[] msgArgs = {Integer.toHexString(adalException.GetState())};
throw new SQLServerException(form.format(msgArgs), null);
}

int millisecondsRemaining = TimerRemaining(timerExpire);
if (ActiveDirectoryAuthentication.GET_ACCESS_TOKEN_TANSISENT_ERROR != errorCategory || timerHasExpired(timerExpire)
|| (sleepInterval >= millisecondsRemaining)) {
int millisecondsRemaining = TimerRemaining(timerExpire);
if (ActiveDirectoryAuthentication.GET_ACCESS_TOKEN_TANSISENT_ERROR != errorCategory || timerHasExpired(timerExpire)
|| (sleepInterval >= millisecondsRemaining)) {

String errorStatus = Integer.toHexString(adalException.GetStatus());
String errorStatus = Integer.toHexString(adalException.GetStatus());

if (connectionlogger.isLoggable(Level.FINER)) {
connectionlogger.fine(toString() + " SQLServerConnection.getFedAuthToken.AdalException category:" + errorCategory
+ " error: " + errorStatus);
}
if (connectionlogger.isLoggable(Level.FINER)) {
connectionlogger.fine(toString() + " SQLServerConnection.getFedAuthToken.AdalException category:" + errorCategory
+ " error: " + errorStatus);
}

MessageFormat form1 = new MessageFormat(SQLServerException.getErrString("R_ADALAuthenticationMiddleErrorMessage"));
String errorCode = Integer.toHexString(adalException.GetStatus()).toUpperCase();
Object[] msgArgs1 = {errorCode, adalException.GetState()};
SQLServerException middleException = new SQLServerException(form1.format(msgArgs1), adalException);
MessageFormat form1 = new MessageFormat(SQLServerException.getErrString("R_ADALAuthenticationMiddleErrorMessage"));
String errorCode = Integer.toHexString(adalException.GetStatus()).toUpperCase();
Object[] msgArgs1 = {errorCode, adalException.GetState()};
SQLServerException middleException = new SQLServerException(form1.format(msgArgs1), adalException);

MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_ADALExecution"));
Object[] msgArgs = {user, authenticationString};
throw new SQLServerException(form.format(msgArgs), null, 0, middleException);
}
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_ADALExecution"));
Object[] msgArgs = {user, authenticationString};
throw new SQLServerException(form.format(msgArgs), null, 0, middleException);
}

if (connectionlogger.isLoggable(Level.FINER)) {
connectionlogger.fine(toString() + " SQLServerConnection.getFedAuthToken sleeping: " + sleepInterval + " milliseconds.");
connectionlogger
.fine(toString() + " SQLServerConnection.getFedAuthToken remaining: " + millisecondsRemaining + " milliseconds.");
}
if (connectionlogger.isLoggable(Level.FINER)) {
connectionlogger.fine(toString() + " SQLServerConnection.getFedAuthToken sleeping: " + sleepInterval + " milliseconds.");
connectionlogger
.fine(toString() + " SQLServerConnection.getFedAuthToken remaining: " + millisecondsRemaining + " milliseconds.");
}

try {
Thread.sleep(sleepInterval);
}
catch (InterruptedException e1) {
// re-interrupt the current thread, in order to restore the thread's interrupt status.
Thread.currentThread().interrupt();
try {
Thread.sleep(sleepInterval);
}
catch (InterruptedException e1) {
// re-interrupt the current thread, in order to restore the thread's interrupt status.
Thread.currentThread().interrupt();
}
sleepInterval = sleepInterval * 2;
}
sleepInterval = sleepInterval * 2;
}
// else choose ADAL4J for integrated authentication. This option is supported for both windows and unix, so we don't need to check the
// OS version here.
else {
fedAuthToken = SQLServerADAL4JUtils.getSqlFedAuthTokenIntegrated(fedAuthInfo, authenticationString);
}
// Break out of the retry loop in successful case.
break;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,6 @@ protected Object[][] getContents() {
{"R_TVPEmptyMetadata", "There are not enough fields in the Structured type. Structured types must have at least one field."},
{"R_TVPInvalidValue", "The value provided for Table-Valued Parameter {0} is not valid. Only SQLServerDataTable, ResultSet and ISQLServerDataRecord objects are supported."},
{"R_TVPInvalidColumnValue", "Input data is not in correct format."},
{"R_AADIntegratedOnNonWindows","ActiveDirectoryIntegrated is only supported on Windows operating systems."},
{"R_TVPSortOrdinalGreaterThanFieldCount", "The sort ordinal {0} on field {1} exceeds the total number of fields."},
{"R_TVPMissingSortOrderOrOrdinal", "The sort order and ordinal must either both be specified, or neither should be specified (SortOrder.Unspecified and -1). The values given were: order = {0}, ordinal = {1}."},
{"R_TVPDuplicateSortOrdinal", "The sort ordinal {0} was specified twice."},
Expand Down