-
Notifications
You must be signed in to change notification settings - Fork 375
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
Adding support for Azure AD access token #837
Changes from all commits
f65173b
06e04b6
aa0bfbb
44bbd33
6bbe864
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 |
---|---|---|
|
@@ -243,6 +243,12 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont | |
} // else driver_version not unknown | ||
#endif // !_WIN32 | ||
|
||
// time to free the access token, if not null | ||
if (conn->azure_ad_access_token != NULL) { | ||
memset(conn->azure_ad_access_token->data, 0, conn->azure_ad_access_token->dataSize); // clear the memory | ||
conn->azure_ad_access_token.reset(); | ||
} | ||
|
||
CHECK_SQL_ERROR( r, conn ) { | ||
throw core::CoreException(); | ||
} | ||
|
@@ -759,35 +765,53 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou | |
{ | ||
bool mars_mentioned = false; | ||
connection_option const* conn_opt; | ||
bool access_token_used = false; | ||
|
||
try { | ||
// First of all, check if access token is specified. If so, check if UID,�PWD,�Authentication exist | ||
// No need to check the keyword Trusted_Connection�because it is not among the acceptable options for SQLSRV drivers | ||
if (zend_hash_index_exists(options, SQLSRV_CONN_OPTION_ACCESS_TOKEN)) { | ||
bool invalidOptions = false; | ||
|
||
// UID and PWD have to be NULLs... throw an exception as long as the user has specified any of them in the connection string, | ||
// even if they may be empty strings. Likewise if the keyword Authentication exists | ||
if (uid != NULL || pwd != NULL || zend_hash_index_exists(options, SQLSRV_CONN_OPTION_AUTHENTICATION)) { | ||
invalidOptions = true; | ||
} | ||
|
||
// Add the server name | ||
common_conn_str_append_func( ODBCConnOptions::SERVER, server, strnlen_s( server ), connection_string TSRMLS_CC ); | ||
|
||
// if uid is not present then we use trusted connection. | ||
if(uid == NULL || strnlen_s( uid ) == 0 ) { | ||
|
||
connection_string += "Trusted_Connection={Yes};"; | ||
} | ||
else { | ||
|
||
bool escaped = core_is_conn_opt_value_escaped( uid, strnlen_s( uid )); | ||
CHECK_CUSTOM_ERROR( !escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) { | ||
CHECK_CUSTOM_ERROR(invalidOptions, conn, SQLSRV_ERROR_INVALID_OPTION_WITH_ACCESS_TOKEN ) { | ||
throw core::CoreException(); | ||
} | ||
|
||
common_conn_str_append_func( ODBCConnOptions::UID, uid, strnlen_s( uid ), connection_string TSRMLS_CC ); | ||
access_token_used = true; | ||
} | ||
|
||
// if no password was given, then don't add a password to the connection string. Perhaps the UID | ||
// given doesn't have a password? | ||
if( pwd != NULL ) { | ||
escaped = core_is_conn_opt_value_escaped( pwd, strnlen_s( pwd )); | ||
CHECK_CUSTOM_ERROR( !escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) { | ||
// Add the server name | ||
common_conn_str_append_func( ODBCConnOptions::SERVER, server, strnlen_s( server ), connection_string TSRMLS_CC ); | ||
|
||
// if uid is not present then we use trusted connection -- but not when access token is used, because they are incompatible | ||
if (!access_token_used) { | ||
if (uid == NULL || strnlen_s(uid) == 0) { | ||
connection_string += CONNECTION_OPTION_NO_CREDENTIALS; // "Trusted_Connection={Yes};" | ||
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. Where is CONNECTION_OPTION_NO_CREDENTIALS defined? 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. At the beginning of the same file. It was there all along but wasn't used. |
||
} | ||
else { | ||
bool escaped = core_is_conn_opt_value_escaped(uid, strnlen_s(uid)); | ||
CHECK_CUSTOM_ERROR(!escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED) { | ||
throw core::CoreException(); | ||
} | ||
|
||
common_conn_str_append_func( ODBCConnOptions::PWD, pwd, strnlen_s( pwd ), connection_string TSRMLS_CC ); | ||
common_conn_str_append_func(ODBCConnOptions::UID, uid, strnlen_s(uid), connection_string TSRMLS_CC); | ||
|
||
// if no password was given, then don't add a password to the connection string. Perhaps the UID | ||
// given doesn't have a password? | ||
if (pwd != NULL) { | ||
escaped = core_is_conn_opt_value_escaped(pwd, strnlen_s(pwd)); | ||
CHECK_CUSTOM_ERROR(!escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED) { | ||
throw core::CoreException(); | ||
} | ||
|
||
common_conn_str_append_func(ODBCConnOptions::PWD, pwd, strnlen_s(pwd), connection_string TSRMLS_CC); | ||
} | ||
} | ||
} | ||
|
||
|
@@ -1172,3 +1196,56 @@ size_t core_str_zval_is_true( _Inout_ zval* value_z ) | |
|
||
return 0; // false | ||
} | ||
|
||
void access_token_set_func::func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC ) | ||
{ | ||
SQLSRV_ASSERT(Z_TYPE_P(value) == IS_STRING, "An access token must be a byte string."); | ||
|
||
size_t value_len = Z_STRLEN_P(value); | ||
|
||
CHECK_CUSTOM_ERROR(value_len <= 0, conn, SQLSRV_ERROR_EMPTY_ACCESS_TOKEN) { | ||
throw core::CoreException(); | ||
} | ||
|
||
const char* value_str = Z_STRVAL_P( value ); | ||
|
||
// The SQL_COPT_SS_ACCESS_TOKEN pre-connection attribute allows the use of an access token (in the format extracted from | ||
// an OAuth JSON response), obtained from Azure AD for authentication instead of username and password, and also | ||
// bypasses the negotiation and obtaining of an access token by the driver. To use an access token, set the | ||
// SQL_COPT_SS_ACCESS_TOKEN connection attribute to a pointer to an ACCESSTOKEN structure | ||
// | ||
// typedef struct AccessToken | ||
// { | ||
// unsigned int dataSize; | ||
// char data[]; | ||
// } ACCESSTOKEN; | ||
// | ||
// NOTE: The ODBC Driver version 13.1 only supports this authentication on Windows. | ||
// | ||
// A valid access token byte string must be expanded so that each byte is followed by a 0 padding byte, | ||
// similar to a UCS-2 string containing only ASCII characters | ||
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. It sounds like the foregoing comments are from a Microsoft Docs document - if so please provide a link to it. 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. ok will do |
||
// | ||
// See https://docs.microsoft.com/sql/connect/odbc/using-azure-active-directory#authenticating-with-an-access-token | ||
|
||
size_t dataSize = 2 * value_len; | ||
|
||
sqlsrv_malloc_auto_ptr<ACCESSTOKEN> accToken; | ||
accToken = reinterpret_cast<ACCESSTOKEN*>(sqlsrv_malloc(sizeof(ACCESSTOKEN) + dataSize)); | ||
|
||
ACCESSTOKEN *pAccToken = accToken.get(); | ||
SQLSRV_ASSERT(pAccToken != NULL, "Something went wrong when trying to allocate memory for the access token."); | ||
|
||
pAccToken->dataSize = dataSize; | ||
|
||
// Expand access token with padding bytes | ||
for (size_t i = 0, j = 0; i < dataSize; i += 2, j++) { | ||
pAccToken->data[i] = value_str[j]; | ||
pAccToken->data[i+1] = 0; | ||
} | ||
|
||
core::SQLSetConnectAttr(conn, SQL_COPT_SS_ACCESS_TOKEN, reinterpret_cast<SQLPOINTER>(pAccToken), SQL_IS_POINTER); | ||
|
||
// Save the pointer because SQLDriverConnect() will use it to make connection to the server | ||
conn->azure_ad_access_token = pAccToken; | ||
accToken.transferred(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
<?php | ||
$accToken = 'TARGET_ACCESS_TOKEN'; | ||
?> |
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.
Is this an ODBC keyword as well?
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.
No, but similar to TraceFile and TraceOn, this one is also a pre-connection attribute.