@@ -22,6 +22,8 @@ def __init__(
2222 self ,
2323 client_id ,
2424 client_secret = None , # Triggers HTTP AUTH for Confidential Client
25+ client_assertion = None , # Assertion for Client Authentication
26+ client_assertion_type = None , # The format of the client_assertion
2527 default_body = None , # a dict to be sent in each token request,
2628 # usually contains Confidential Client authentication parameters
2729 # such as {'client_id': 'your_id', 'client_secret': 'secret'}
@@ -31,6 +33,13 @@ def __init__(
3133 """Initialize a client object to talk all the OAuth2 grants to the server.
3234
3335 Args:
36+ client_assertion (str):
37+ The client assertion to authenticate this client, per RFC 7521.
38+ client_assertion_type (str):
39+ If you leave it as the default None, this method will try to make
40+ a guess between SAML2 (RFC 7522) and JWT (RFC 7523),
41+ the only two profiles defined in RFC 7521.
42+ But you can also explicitly provide a value, if needed.
3443 configuration (dict):
3544 It contains the configuration (i.e. metadata) of the auth server.
3645 The actual content typically contains keys like
@@ -44,6 +53,13 @@ def __init__(
4453 self .client_id = client_id
4554 self .client_secret = client_secret
4655 self .default_body = default_body or {}
56+ if client_assertion is not None : # See https://tools.ietf.org/html/rfc7521#section-4.2
57+ TYPE_JWT = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
58+ TYPE_SAML2 = "urn:ietf:params:oauth:client-assertion-type:saml2-bearer"
59+ if client_assertion_type is None : # RFC7521 defines only 2 profiles
60+ client_assertion_type = TYPE_JWT if "." in client_assertion else TYPE_SAML2
61+ self .default_body ["client_assertion" ] = client_assertion
62+ self .default_body ["client_assertion_type" ] = client_assertion_type
4763 self .configuration = configuration or {}
4864 self .logger = logging .getLogger (__name__ )
4965
@@ -132,6 +148,8 @@ class Client(BaseClient): # We choose to implement all 4 grants in 1 class
132148 "DEVICE_CODE" : "device_code" ,
133149 }
134150 DEVICE_FLOW_RETRIABLE_ERRORS = ("authorization_pending" , "slow_down" )
151+ GRANT_TYPE_SAML2 = "urn:ietf:params:oauth:grant-type:saml2-bearer" # RFC7522
152+ GRANT_TYPE_JWT = "urn:ietf:params:oauth:grant-type:jwt-bearer" # RFC7523
135153
136154 def initiate_device_flow (self , scope = None , ** kwargs ):
137155 # type: (list, **dict) -> dict
@@ -348,3 +366,23 @@ def obtain_token_with_refresh_token(self, token_item, scope=None,
348366 return resp
349367 raise ValueError ("token_item should not be a type %s" % type (token_item ))
350368
369+ def obtain_token_with_assertion (
370+ self , assertion , grant_type = None , scope = None , ** kwargs ):
371+ # type: (str, Union[str, None], Union[str, list, set, tuple]) -> dict
372+ """This method implements Assertion Framework for OAuth2 (RFC 7521).
373+ See details at https://tools.ietf.org/html/rfc7521#section-4.1
374+
375+ :param assertion: The assertion string which will be sent on wire as-is
376+ :param grant_type:
377+ If you leave it as the default None, this method will try to make
378+ a guess between SAML2 (RFC 7522) and JWT (RFC 7523),
379+ the only two profiles defined in RFC 7521.
380+ But you can also explicitly provide a value, if needed.
381+ :param scope: Optional. It must be a subset of previously granted scopes.
382+ """
383+ if grant_type is None :
384+ grant_type = self .GRANT_TYPE_JWT if "." in assertion else self .GRANT_TYPE_SAML2
385+ data = kwargs .pop ("data" , {})
386+ data .update (scope = scope , assertion = assertion )
387+ return self ._obtain_token (grant_type , data = data , ** kwargs )
388+
0 commit comments