34
34
35
35
import oauth2client
36
36
from oauth2client import _helpers
37
+ from oauth2client import _pkce
37
38
from oauth2client import clientsecrets
38
39
from oauth2client import transport
39
40
@@ -1632,7 +1633,9 @@ def credentials_from_code(client_id, client_secret, scope, code,
1632
1633
auth_uri = oauth2client .GOOGLE_AUTH_URI ,
1633
1634
revoke_uri = oauth2client .GOOGLE_REVOKE_URI ,
1634
1635
device_uri = oauth2client .GOOGLE_DEVICE_URI ,
1635
- token_info_uri = oauth2client .GOOGLE_TOKEN_INFO_URI ):
1636
+ token_info_uri = oauth2client .GOOGLE_TOKEN_INFO_URI ,
1637
+ pkce = False ,
1638
+ code_verifier = None ):
1636
1639
"""Exchanges an authorization code for an OAuth2Credentials object.
1637
1640
1638
1641
Args:
@@ -1656,6 +1659,15 @@ def credentials_from_code(client_id, client_secret, scope, code,
1656
1659
device_uri: string, URI for device authorization endpoint. For
1657
1660
convenience defaults to Google's endpoints but any OAuth
1658
1661
2.0 provider can be used.
1662
+ pkce: boolean, default: False, Generate and include a "Proof Key
1663
+ for Code Exchange" (PKCE) with your authorization and token
1664
+ requests. This adds security for installed applications that
1665
+ cannot protect a client_secret. See RFC 7636 for details.
1666
+ code_verifier: bytestring or None, default: None, parameter passed
1667
+ as part of the code exchange when pkce=True. If
1668
+ None, a code_verifier will automatically be
1669
+ generated as part of step1_get_authorize_url(). See
1670
+ RFC 7636 for details.
1659
1671
1660
1672
Returns:
1661
1673
An OAuth2Credentials object.
@@ -1666,10 +1678,14 @@ def credentials_from_code(client_id, client_secret, scope, code,
1666
1678
"""
1667
1679
flow = OAuth2WebServerFlow (client_id , client_secret , scope ,
1668
1680
redirect_uri = redirect_uri ,
1669
- user_agent = user_agent , auth_uri = auth_uri ,
1670
- token_uri = token_uri , revoke_uri = revoke_uri ,
1681
+ user_agent = user_agent ,
1682
+ auth_uri = auth_uri ,
1683
+ token_uri = token_uri ,
1684
+ revoke_uri = revoke_uri ,
1671
1685
device_uri = device_uri ,
1672
- token_info_uri = token_info_uri )
1686
+ token_info_uri = token_info_uri ,
1687
+ pkce = pkce ,
1688
+ code_verifier = code_verifier )
1673
1689
1674
1690
credentials = flow .step2_exchange (code , http = http )
1675
1691
return credentials
@@ -1704,6 +1720,15 @@ def credentials_from_clientsecrets_and_code(filename, scope, code,
1704
1720
cache: An optional cache service client that implements get() and set()
1705
1721
methods. See clientsecrets.loadfile() for details.
1706
1722
device_uri: string, OAuth 2.0 device authorization endpoint
1723
+ pkce: boolean, default: False, Generate and include a "Proof Key
1724
+ for Code Exchange" (PKCE) with your authorization and token
1725
+ requests. This adds security for installed applications that
1726
+ cannot protect a client_secret. See RFC 7636 for details.
1727
+ code_verifier: bytestring or None, default: None, parameter passed
1728
+ as part of the code exchange when pkce=True. If
1729
+ None, a code_verifier will automatically be
1730
+ generated as part of step1_get_authorize_url(). See
1731
+ RFC 7636 for details.
1707
1732
1708
1733
Returns:
1709
1734
An OAuth2Credentials object.
@@ -1807,6 +1832,8 @@ def __init__(self, client_id,
1807
1832
device_uri = oauth2client .GOOGLE_DEVICE_URI ,
1808
1833
token_info_uri = oauth2client .GOOGLE_TOKEN_INFO_URI ,
1809
1834
authorization_header = None ,
1835
+ pkce = False ,
1836
+ code_verifier = None ,
1810
1837
** kwargs ):
1811
1838
"""Constructor for OAuth2WebServerFlow.
1812
1839
@@ -1844,6 +1871,15 @@ def __init__(self, client_id,
1844
1871
require a client to authenticate using a
1845
1872
header value instead of passing client_secret
1846
1873
in the POST body.
1874
+ pkce: boolean, default: False, Generate and include a "Proof Key
1875
+ for Code Exchange" (PKCE) with your authorization and token
1876
+ requests. This adds security for installed applications that
1877
+ cannot protect a client_secret. See RFC 7636 for details.
1878
+ code_verifier: bytestring or None, default: None, parameter passed
1879
+ as part of the code exchange when pkce=True. If
1880
+ None, a code_verifier will automatically be
1881
+ generated as part of step1_get_authorize_url(). See
1882
+ RFC 7636 for details.
1847
1883
**kwargs: dict, The keyword arguments are all optional and required
1848
1884
parameters for the OAuth calls.
1849
1885
"""
@@ -1863,6 +1899,8 @@ def __init__(self, client_id,
1863
1899
self .device_uri = device_uri
1864
1900
self .token_info_uri = token_info_uri
1865
1901
self .authorization_header = authorization_header
1902
+ self ._pkce = pkce
1903
+ self .code_verifier = code_verifier
1866
1904
self .params = _oauth2_web_server_flow_params (kwargs )
1867
1905
1868
1906
@_helpers .positional (1 )
@@ -1903,6 +1941,13 @@ def step1_get_authorize_url(self, redirect_uri=None, state=None):
1903
1941
query_params ['state' ] = state
1904
1942
if self .login_hint is not None :
1905
1943
query_params ['login_hint' ] = self .login_hint
1944
+ if self ._pkce :
1945
+ if not self .code_verifier :
1946
+ self .code_verifier = _pkce .code_verifier ()
1947
+ challenge = _pkce .code_challenge (self .code_verifier )
1948
+ query_params ['code_challenge' ] = challenge
1949
+ query_params ['code_challenge_method' ] = 'S256'
1950
+
1906
1951
query_params .update (self .params )
1907
1952
return _update_query_params (self .auth_uri , query_params )
1908
1953
@@ -1997,6 +2042,8 @@ def step2_exchange(self, code=None, http=None, device_flow_info=None):
1997
2042
}
1998
2043
if self .client_secret is not None :
1999
2044
post_data ['client_secret' ] = self .client_secret
2045
+ if self ._pkce :
2046
+ post_data ['code_verifier' ] = self .code_verifier
2000
2047
if device_flow_info is not None :
2001
2048
post_data ['grant_type' ] = 'http://oauth.net/grant_type/device/1.0'
2002
2049
else :
@@ -2054,7 +2101,7 @@ def step2_exchange(self, code=None, http=None, device_flow_info=None):
2054
2101
@_helpers .positional (2 )
2055
2102
def flow_from_clientsecrets (filename , scope , redirect_uri = None ,
2056
2103
message = None , cache = None , login_hint = None ,
2057
- device_uri = None ):
2104
+ device_uri = None , pkce = None , code_verifier = None ):
2058
2105
"""Create a Flow from a clientsecrets file.
2059
2106
2060
2107
Will create the right kind of Flow based on the contents of the
@@ -2103,10 +2150,11 @@ def flow_from_clientsecrets(filename, scope, redirect_uri=None,
2103
2150
'login_hint' : login_hint ,
2104
2151
}
2105
2152
revoke_uri = client_info .get ('revoke_uri' )
2106
- if revoke_uri is not None :
2107
- constructor_kwargs ['revoke_uri' ] = revoke_uri
2108
- if device_uri is not None :
2109
- constructor_kwargs ['device_uri' ] = device_uri
2153
+ optional = ('revoke_uri' , 'device_uri' , 'pkce' , 'code_verifier' )
2154
+ for param in optional :
2155
+ if locals ()[param ] is not None :
2156
+ constructor_kwargs [param ] = locals ()[param ]
2157
+
2110
2158
return OAuth2WebServerFlow (
2111
2159
client_info ['client_id' ], client_info ['client_secret' ],
2112
2160
scope , ** constructor_kwargs )
0 commit comments