-
Notifications
You must be signed in to change notification settings - Fork 435
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 password on non windows #146
Changes from 6 commits
397eca0
48e166a
7ba7ff7
a467b94
1a80f96
2c33841
4878161
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package com.microsoft.sqlserver.jdbc; | ||
|
||
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 com.microsoft.aad.adal4j.AuthenticationContext; | ||
import com.microsoft.aad.adal4j.AuthenticationException; | ||
import com.microsoft.aad.adal4j.AuthenticationResult; | ||
import com.microsoft.sqlserver.jdbc.SQLServerConnection.ActiveDirectoryAuthentication; | ||
import com.microsoft.sqlserver.jdbc.SQLServerConnection.SqlFedAuthInfo; | ||
|
||
class SQLServerADAL4JUtils { | ||
|
||
static SqlFedAuthToken getSqlFedAuthToken(SqlFedAuthInfo fedAuthInfo, | ||
String user, | ||
String password, | ||
String authenticationString) throws SQLServerException { | ||
ExecutorService executorService = Executors.newFixedThreadPool(1); | ||
try { | ||
AuthenticationContext context = new AuthenticationContext(fedAuthInfo.stsurl, false, executorService); | ||
Future<AuthenticationResult> future = context.acquireToken(fedAuthInfo.spn, ActiveDirectoryAuthentication.jdbcFedauthClientId, user, | ||
password, null); | ||
|
||
AuthenticationResult authenticationResult = future.get(); | ||
SqlFedAuthToken fedAuthToken = new SqlFedAuthToken(authenticationResult.getAccessToken(), authenticationResult.getExpiresOnDate()); | ||
|
||
return fedAuthToken; | ||
} | ||
catch (MalformedURLException | InterruptedException e) { | ||
throw new SQLServerException(e.getMessage(), null); | ||
} | ||
catch (ExecutionException e) { | ||
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_ADALExecution")); | ||
Object[] msgArgs = {user, authenticationString}; | ||
|
||
// 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(); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,7 +37,6 @@ | |
import java.sql.Struct; | ||
import java.text.MessageFormat; | ||
import java.util.Arrays; | ||
import java.util.Date; | ||
import java.util.Enumeration; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
|
@@ -137,40 +136,24 @@ class FederatedAuthenticationFeatureExtensionData { | |
} | ||
|
||
class SqlFedAuthInfo { | ||
private String spn; | ||
private String stsurl; | ||
String spn; | ||
String stsurl; | ||
|
||
@Override | ||
public String toString() { | ||
return "STSURL: " + stsurl + ", SPN: " + spn; | ||
} | ||
} | ||
|
||
final class SqlFedAuthToken { | ||
private final Date expiresOn; | ||
private final String accessToken; | ||
|
||
|
||
SqlFedAuthToken(final String accessToken, | ||
final long expiresIn) { | ||
this.accessToken = accessToken; | ||
|
||
Date now = new Date(); | ||
now.setTime(now.getTime() + (expiresIn * 1000)); | ||
this.expiresOn = now; | ||
} | ||
|
||
Date getExpiresOnDate() { | ||
return expiresOn; | ||
} | ||
} | ||
|
||
private class ActiveDirectoryAuthentication { | ||
private static final String jdbcFedauthClientId = "7f98cb04-cd1e-40df-9140-3bf7e2cea4db"; | ||
private static final String AdalGetAccessTokenFunctionName = "ADALGetAccessToken"; | ||
private static final int GetAccessTokenSuccess = 0; | ||
private static final int GetAccessTokenInvalidGrant = 1; | ||
private static final int GetAccessTokenTansisentError = 2; | ||
private static final int GetAccessTokenOtherError = 3; | ||
class ActiveDirectoryAuthentication { | ||
static final String jdbcFedauthClientId = "7f98cb04-cd1e-40df-9140-3bf7e2cea4db"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. final static instance variables should be in CAPS There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
static final String AdalGetAccessTokenFunctionName = "ADALGetAccessToken"; | ||
static final int GetAccessTokenSuccess = 0; | ||
static final int GetAccessTokenInvalidGrant = 1; | ||
static final int GetAccessTokenTansisentError = 2; | ||
static final int GetAccessTokenOtherError = 3; | ||
} | ||
|
||
/** | ||
|
@@ -1270,8 +1253,8 @@ Connection connectInternal(Properties propsIn, | |
} | ||
|
||
if ((!System.getProperty("os.name").toLowerCase().startsWith("windows")) | ||
&& (!authenticationString.equalsIgnoreCase(SqlAuthentication.NotSpecified.toString()))) { | ||
throw new SQLServerException(SQLServerException.getErrString("R_FedAuthOnNonWindows"), null); | ||
&& (authenticationString.equalsIgnoreCase(SqlAuthentication.ActiveDirectoryIntegrated.toString()))) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in getFedAuthToken(..) we have similar check of ActiveDirectoryPassword. There we used authenticationString.trim(). Is there any possibility that we are getting authenticationString as null? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no, the default is NotSpecified, it cannot be null. you can search for this line in the file and you will see: |
||
throw new SQLServerException(SQLServerException.getErrString("R_AADIntegratedOnNonWindows"), null); | ||
} | ||
|
||
sPropKey = SQLServerDriverStringProperty.WORKSTATION_ID.toString(); | ||
|
@@ -3441,6 +3424,8 @@ void onFedAuthInfo(SqlFedAuthInfo fedAuthInfo, | |
} | ||
|
||
private SqlFedAuthToken getFedAuthToken(SqlFedAuthInfo fedAuthInfo) throws SQLServerException { | ||
SqlFedAuthToken fedAuthToken = null; | ||
|
||
// fedAuthInfo should not be null. | ||
assert null != fedAuthInfo; | ||
|
||
|
@@ -3450,83 +3435,83 @@ private SqlFedAuthToken getFedAuthToken(SqlFedAuthInfo fedAuthInfo) throws SQLSe | |
// No:of attempts, for tracing purposes, if we underwent retries. | ||
int numberOfAttempts = 0; | ||
|
||
FedAuthDllInfo dllInfo = null; | ||
|
||
String user = activeConnectionProperties.getProperty(SQLServerDriverStringProperty.USER.toString()); | ||
String password = activeConnectionProperties.getProperty(SQLServerDriverStringProperty.PASSWORD.toString()); | ||
|
||
long expirationFileTime = 0; | ||
|
||
while (true) { | ||
numberOfAttempts++; | ||
|
||
try { | ||
if (authenticationString.trim().equalsIgnoreCase(SqlAuthentication.ActiveDirectoryPassword.toString())) { | ||
dllInfo = AuthenticationJNI.getAccessToken(user, password, fedAuthInfo.stsurl, fedAuthInfo.spn, clientConnectionId.toString(), | ||
ActiveDirectoryAuthentication.jdbcFedauthClientId, expirationFileTime); | ||
} | ||
else if (authenticationString.trim().equalsIgnoreCase(SqlAuthentication.ActiveDirectoryIntegrated.toString())) { | ||
dllInfo = AuthenticationJNI.getAccessTokenForWindowsIntegrated(fedAuthInfo.stsurl, fedAuthInfo.spn, clientConnectionId.toString(), | ||
ActiveDirectoryAuthentication.jdbcFedauthClientId, expirationFileTime); | ||
} | ||
|
||
// AccessToken should not be null. | ||
assert null != dllInfo.accessTokenBytes; | ||
if (authenticationString.trim().equalsIgnoreCase(SqlAuthentication.ActiveDirectoryPassword.toString())) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Better to have constant on left side while comparing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I totally agree, but there are a lot of places to change in the driver, maybe we can have a separate PR for this? |
||
fedAuthToken = SQLServerADAL4JUtils.getSqlFedAuthToken(fedAuthInfo, user, password, authenticationString); | ||
|
||
// Break out of the retry loop in successful case. | ||
break; | ||
} | ||
catch (DLLException adalException) { | ||
else if (authenticationString.trim().equalsIgnoreCase(SqlAuthentication.ActiveDirectoryIntegrated.toString())) { | ||
try { | ||
long expirationFileTime = 0; | ||
FedAuthDllInfo dllInfo = AuthenticationJNI.getAccessTokenForWindowsIntegrated(fedAuthInfo.stsurl, fedAuthInfo.spn, | ||
clientConnectionId.toString(), ActiveDirectoryAuthentication.jdbcFedauthClientId, expirationFileTime); | ||
|
||
// 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); | ||
} | ||
// AccessToken should not be null. | ||
assert null != dllInfo.accessTokenBytes; | ||
|
||
int millisecondsRemaining = TimerRemaining(timerExpire); | ||
if (ActiveDirectoryAuthentication.GetAccessTokenTansisentError != errorCategory || timerHasExpired(timerExpire) | ||
|| (sleepInterval >= millisecondsRemaining)) { | ||
byte[] accessTokenFromDLL = dllInfo.accessTokenBytes; | ||
|
||
String errorStatus = Integer.toHexString(adalException.GetStatus()); | ||
String accessToken = new String(accessTokenFromDLL, UTF_16LE); | ||
|
||
if (connectionlogger.isLoggable(Level.FINER)) { | ||
connectionlogger.fine(toString() + " SQLServerConnection.getFedAuthToken.AdalException category:" + errorCategory + " error: " | ||
+ errorStatus); | ||
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); | ||
} | ||
|
||
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); | ||
int millisecondsRemaining = TimerRemaining(timerExpire); | ||
if (ActiveDirectoryAuthentication.GetAccessTokenTansisentError != errorCategory || timerHasExpired(timerExpire) | ||
|| (sleepInterval >= millisecondsRemaining)) { | ||
|
||
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_ADALExecution")); | ||
Object[] msgArgs = {user, authenticationString}; | ||
throw new SQLServerException(form.format(msgArgs), null, 0, middleException); | ||
} | ||
String errorStatus = Integer.toHexString(adalException.GetStatus()); | ||
|
||
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.AdalException category:" + errorCategory | ||
+ " error: " + errorStatus); | ||
} | ||
|
||
try { | ||
Thread.sleep(sleepInterval); | ||
} | ||
catch (InterruptedException e1) { | ||
// continue if the thread is interrupted. This really should not happen. | ||
} | ||
sleepInterval = sleepInterval * 2; | ||
} | ||
} | ||
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); | ||
|
||
byte[] accessTokenFromDLL = dllInfo.accessTokenBytes; | ||
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_ADALExecution")); | ||
Object[] msgArgs = {user, authenticationString}; | ||
throw new SQLServerException(form.format(msgArgs), null, 0, middleException); | ||
} | ||
|
||
String accessToken = new String(accessTokenFromDLL, UTF_16LE); | ||
if (connectionlogger.isLoggable(Level.FINER)) { | ||
connectionlogger.fine(toString() + " SQLServerConnection.getFedAuthToken sleeping: " + sleepInterval + " milliseconds."); | ||
connectionlogger | ||
.fine(toString() + " SQLServerConnection.getFedAuthToken remaining: " + millisecondsRemaining + " milliseconds."); | ||
} | ||
|
||
SqlFedAuthToken fedAuthToken = new SqlFedAuthToken(accessToken, dllInfo.expiresIn); | ||
try { | ||
Thread.sleep(sleepInterval); | ||
} | ||
catch (InterruptedException e1) { | ||
// continue if the thread is interrupted. This really should not happen. | ||
} | ||
sleepInterval = sleepInterval * 2; | ||
} | ||
} | ||
} | ||
|
||
return fedAuthToken; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package com.microsoft.sqlserver.jdbc; | ||
|
||
import java.util.Date; | ||
|
||
class SqlFedAuthToken { | ||
final Date expiresOn; | ||
final String accessToken; | ||
|
||
SqlFedAuthToken(final String accessToken, | ||
final long expiresIn) { | ||
this.accessToken = accessToken; | ||
|
||
Date now = new Date(); | ||
now.setTime(now.getTime() + (expiresIn * 1000)); | ||
this.expiresOn = now; | ||
} | ||
|
||
SqlFedAuthToken(final String accessToken, | ||
final Date expiresOn) { | ||
this.accessToken = accessToken; | ||
this.expiresOn = expiresOn; | ||
} | ||
|
||
Date getExpiresOnDate() { | ||
return expiresOn; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure you do not want public class? By default this will use package level visibility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why would we want it to be public?