-
Notifications
You must be signed in to change notification settings - Fork 205
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
client_assertion can accept a callback #747
base: dev
Are you sure you want to change the base?
Conversation
Disabled SSH Cert when using broker
Implement instance_discovery=False
Bump version number
Merge Release 1.19.0 back to dev branch
Apply the refactor to similar code path
Broker integration
Bumping version number
Merge Release 1.20.0 back to dev
Test matrix covers Python 3.11
Switch to new region endpoints
Fallback to expires_on when expires_in is absent
CAE team and MSI team are working on turning on CAE by default for MSI v1. So what that means is, App developers will start seeing CAE even without setting the capability - "CP1". Update msal/application.py Co-authored-by: Den Delimarsky <53200638+localden@users.noreply.github.com> Update msal/application.py Co-authored-by: Den Delimarsky <53200638+localden@users.noreply.github.com> Update msal/application.py Co-authored-by: Den Delimarsky <53200638+localden@users.noreply.github.com> Update msal/managed_identity.py Co-authored-by: Den Delimarsky <53200638+localden@users.noreply.github.com> Update msal/managed_identity.py Co-authored-by: Den Delimarsky <53200638+localden@users.noreply.github.com> Update msal/managed_identity.py Co-authored-by: Den Delimarsky <53200638+localden@users.noreply.github.com> Update msal/managed_identity.py Co-authored-by: Den Delimarsky <53200638+localden@users.noreply.github.com>
* Add/update broker integration doc * Move the doc components from .md file to broker-test.py
Old ciam2 test tenant is obsolete
Resource ID as mi_res_id in App Service, as msi_res_id in other flavors
…ring This addresses 705 and 746
@@ -757,6 +780,11 @@ def _build_client(self, client_credential, authority, skip_regional_client=False | |||
# Use client_credential.get("...") rather than "..." in client_credential | |||
# so that we can ignore an empty string came from an empty ENV VAR. | |||
if client_credential.get("client_assertion"): | |||
if isinstance(client_credential["client_assertion"], str): | |||
warnings.warn( | |||
"Please use a callback instead of a string. See also " |
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.
"Please use a callback instead of a string. See also " | |
"Use a callback instead of a string. MSAL will invoke the callback every time it needs to fetch a token from the identity provider. The implementer is responsible for returning non-expired assertion and for caching assertions. See also " |
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.
Will accept this suggestion in my next commit.
We might as well also append "... with the help of a helper AutoRefresher
. See also ...".
I'm providing more explanation about the AutoRefresher in the last comment below.
|
||
import msal | ||
|
||
def assertion_callback(): |
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.
Can you add claims as input to this callback? This will help implementers of FIC perform token revocation, where the claims need to be take into account by the assertion provider as well.
May be separate PR, but want to make sure adding parametewrs to this is not a breaking change.
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.
An adjustable callback can be created as a class, so that it can maintain its state. The crux is that the MSAL code path calling this callback does not know what proper values to feed in. So, the app developer - or a higher-level API - will need to expect an assertion error from eSTS, and then adjust that callback instance accordingly.
class AdjustsableCallback:
extra_claims = None
def callback(self):
return "A new assertion based on extra claims: " + self.extra_claims
ac = AdjustableCallback()
cca = ConfidentialClientApplication(..., client_credential={"client_assertion": ac.callback})
result = cca.acquire_token_for_client(...)
if result.get("error"):
ac.extra_claims = get_new_claims_somehow()
result = cca.acquire_token_for_client(...)
Do you want me to also document this advance scenario into the docs this time?
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.
The claims have to flow from MSAL's acquire_token_for_client call into the callback. An example would be good.
Here's the equivalent .NET code
// singleton
var cca = ConfidentialCLientApp(...)
.WithClientAssertion(
(input) => {
string claims = input.Claims;
GetAssertionFromMsiOrWhatever(claims);
});
// later in the code
var authResult = cca.acquireTokenForClient(); // ok, callback invoked, claims is empty
// call downstream API
// 401 + WWW-AUthenticate returned
// parse header and extract claims
// get new token with claims challenge
authResult = cca.acquireTokenForClient(claims); // ok, callback is invoked, claims is not empty
|
||
smart_callback = msal.AutoRefresher( | ||
assertion_callback, | ||
expires_in=3600, # Whatever value that you see fit |
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.
Not sure what this auto refresher is. We should mention FIC scenarios in the docs instead.
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.
Not sure what this auto refresher is.
Naming is hard, and new name suggestions are welcome (perhaps CallbackCache
instead?). But the functionality behind the "auto refresher" was needed since day 1, because even when in the legit authenticated-by-a-cert scenario, MSAL Python internally has (1) a helper function to use that cert to create a short-lived assertion, and (2) a helper class AutoRefresher
to cache the callback outcome and only call the callback again after a predefined expiry time elapsed. Such a cache behavior is desirable for the #2
scenario described in this comment.
In this PR, we repurpose the #1
callback behavior to accept any callback; and expose the #2
cache behavior so that app developers don't need to reinvent the wheel.
We should mention FIC scenarios in the docs instead.
I suppose, the #1
callback can be used in any scenario including FIC, but the #2
cache concept shall always be applicable.
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.
FIC example would be good. You can use example from here: https://review.learn.microsoft.com/en-us/identity/microsoft-identity-platform/federated-identity-credentials?branch=main
3d6e831
to
8a136b2
Compare
secret_name = parse_qs(secret_url.query)["Secret"][0] | ||
else: # Ciam6 era has a URL path that ends with the secret name | ||
secret_name = secret_url.path.split("/")[-1] | ||
logger.info('Detected secret name "%s" from "%s"', secret_name, raw_url) |
Check failure
Code scanning / CodeQL
Clear-text logging of sensitive information High test
sensitive data (secret)
This expression logs
sensitive data (secret)
This expression logs
sensitive data (secret)
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix AI 2 months ago
To fix the problem, we should avoid logging the sensitive secret_name
directly. Instead, we can log a generic message that indicates the presence of a secret without revealing its actual value. This approach maintains the usefulness of the log for debugging purposes while protecting sensitive information.
- Replace the logging statement that logs
secret_name
with a more generic message. - Ensure that the new log message does not contain any sensitive information.
-
Copy modified line R1051
@@ -1050,3 +1050,3 @@ | ||
secret_name = secret_url.path.split("/")[-1] | ||
logger.info('Detected secret name "%s" from "%s"', secret_name, raw_url) | ||
logger.info('Detected a secret from the provided URL.') | ||
self._test_acquire_token_for_client( |
secret_name = parse_qs(secret_url.query)["Secret"][0] | ||
else: # Ciam6 era has a URL path that ends with the secret name | ||
secret_name = secret_url.path.split("/")[-1] | ||
logger.info('Detected secret name "%s" from "%s"', secret_name, raw_url) |
Check failure
Code scanning / CodeQL
Clear-text logging of sensitive information High test
sensitive data (secret)
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix AI 2 months ago
To fix the problem, we should avoid logging the sensitive raw_url
directly. Instead, we can log a sanitized version of the URL that does not include the sensitive parts. One approach is to log only the non-sensitive components of the URL, such as the scheme, netloc, and path, while omitting the query parameters and fragment.
- Modify the logging statement on line 1051 to exclude sensitive information.
- Introduce a helper function to sanitize the URL before logging.
-
Copy modified lines R1051-R1052
@@ -1050,3 +1050,4 @@ | ||
secret_name = secret_url.path.split("/")[-1] | ||
logger.info('Detected secret name "%s" from "%s"', secret_name, raw_url) | ||
sanitized_url = f"{secret_url.scheme}://{secret_url.netloc}{secret_url.path}" | ||
logger.info('Detected secret name "%s" from sanitized URL "%s"', secret_name, sanitized_url) | ||
self._test_acquire_token_for_client( |
@@ -0,0 +1,51 @@ | |||
-----BEGIN ENCRYPTED PRIVATE KEY----- |
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.
Does msal Python utilize CI pipeline? Can you get these test certs from the lab instead of committing them here?
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.
Those certs are not certs for any real services. We just created some dummy certs for testing the cert loading code path. Thanks for your review, though. :)
Note: As an aftermath of all commits being rewritten, this PR currently contains |
client_assertion
can accept a callback.AutoRefresher
and document their usage. The new doc is staged here, just search the keyword "Supporting raw assertion obtained from elsewhere"client_assertion
as a stringThis will close #705 and close #746