Skip to content

Commit 33a5483

Browse files
authored
Implement wrapper method of issue_stateless_channel_token (#981)
## Summary - Add convenience wrapper methods `issue_stateless_channel_token_by_jwt_assertion()` and `issue_stateless_channel_token_by_client_secret()` - The stateless token API has two authentication patterns (`oneOf`), but the generated `issue_stateless_channel_token()` merges them into a single method where all parameters are required. Previously, users had to pass empty strings for unused parameters. These wrappers auto-fill them. ## Test plan - [x] `tests/api/test_issue_stateless_channel_token.py` — verifies correct form parameters are sent for each authentication pattern Resolves #823
1 parent f0d222b commit 33a5483

File tree

4 files changed

+418
-0
lines changed

4 files changed

+418
-0
lines changed

generate-code.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,82 @@ def run_command(command):
1919
return proc.stdout.strip()
2020

2121

22+
def add_stateless_channel_token_wrappers():
23+
for fname in ['channel_access_token.py', 'async_channel_access_token.py']:
24+
filepath = f'linebot/v3/oauth/api/{fname}'
25+
26+
# Inject deprecation notes into original methods' docstrings
27+
with open(filepath, 'r') as fp:
28+
lines = fp.readlines()
29+
30+
new_lines = []
31+
i = 0
32+
while i < len(lines):
33+
new_lines.append(lines[i])
34+
for base_method in ['issue_stateless_channel_token_with_http_info',
35+
'issue_stateless_channel_token']:
36+
if f'def {base_method}(self' in lines[i] and i + 1 < len(lines) and '"""' in lines[i + 1]:
37+
# Next line: docstring title
38+
i += 1
39+
new_lines.append(lines[i])
40+
# Next line: blank line
41+
i += 1
42+
new_lines.append(lines[i])
43+
# Insert deprecation note
44+
new_lines.append(f' .. deprecated::\n')
45+
new_lines.append(f' Use :func:`{base_method}_by_jwt_assertion` or\n')
46+
new_lines.append(f' :func:`{base_method}_by_client_secret` instead.\n')
47+
new_lines.append('\n')
48+
break
49+
i += 1
50+
51+
with open(filepath, 'w') as fp:
52+
fp.writelines(new_lines)
53+
54+
# Append wrapper methods with docstrings
55+
with open(filepath, 'a') as fp:
56+
for base_method in ['issue_stateless_channel_token',
57+
'issue_stateless_channel_token_with_http_info']:
58+
if base_method == 'issue_stateless_channel_token':
59+
rtype = 'IssueStatelessChannelAccessTokenResponse'
60+
else:
61+
rtype = 'ApiResponse'
62+
63+
fp.write("\n")
64+
fp.write(f" def {base_method}_by_jwt_assertion(self, client_assertion, **kwargs):\n")
65+
fp.write(f' """Issue a stateless channel access token using a JSON Web Token (JWT).\n')
66+
fp.write(f'\n')
67+
fp.write(f' :param str client_assertion: A JSON Web Token the client needs to create and sign with the private key of the Assertion Signing Key.\n')
68+
fp.write(f' :return: Returns the result object.\n')
69+
fp.write(f' :rtype: {rtype}\n')
70+
fp.write(f' """\n')
71+
fp.write(f" return self.{base_method}(\n")
72+
fp.write(" grant_type='client_credentials',\n")
73+
fp.write(" client_assertion_type='urn:ietf:params:oauth:client-assertion-type:jwt-bearer',\n")
74+
fp.write(" client_assertion=client_assertion,\n")
75+
fp.write(" client_id='',\n")
76+
fp.write(" client_secret='',\n")
77+
fp.write(" **kwargs,\n")
78+
fp.write(" )\n")
79+
fp.write("\n")
80+
fp.write(f" def {base_method}_by_client_secret(self, client_id, client_secret, **kwargs):\n")
81+
fp.write(f' """Issue a stateless channel access token using client ID and client secret.\n')
82+
fp.write(f'\n')
83+
fp.write(f' :param str client_id: Channel ID.\n')
84+
fp.write(f' :param str client_secret: Channel secret.\n')
85+
fp.write(f' :return: Returns the result object.\n')
86+
fp.write(f' :rtype: {rtype}\n')
87+
fp.write(f' """\n')
88+
fp.write(f" return self.{base_method}(\n")
89+
fp.write(" grant_type='client_credentials',\n")
90+
fp.write(" client_assertion_type='',\n")
91+
fp.write(" client_assertion='',\n")
92+
fp.write(" client_id=client_id,\n")
93+
fp.write(" client_secret=client_secret,\n")
94+
fp.write(" **kwargs,\n")
95+
fp.write(" )\n")
96+
97+
2298
def rewrite_liff_function_name_backward_compats():
2399
for fname in ['liff.py', 'async_liff.py']:
24100
with open(f'linebot/v3/liff/api/{fname}', 'a') as fp:
@@ -98,6 +174,8 @@ def main():
98174
run_command(command)
99175

100176

177+
add_stateless_channel_token_wrappers()
178+
101179
## TODO(v4): Delete this workaround in v4. This workaround keeps backward compatibility.
102180
rewrite_liff_function_name_backward_compats()
103181

linebot/v3/oauth/api/async_channel_access_token.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,10 @@ def issue_stateless_channel_token(self, grant_type : Annotated[StrictStr, Field(
570570
def issue_stateless_channel_token(self, grant_type : Annotated[StrictStr, Field(..., description="`client_credentials`")], client_assertion_type : Annotated[StrictStr, Field(..., description="URL-encoded value of `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`")], client_assertion : Annotated[StrictStr, Field(..., description="A JSON Web Token the client needs to create and sign with the private key of the Assertion Signing Key.")], client_id : Annotated[StrictStr, Field(..., description="Channel ID.")], client_secret : Annotated[StrictStr, Field(..., description="Channel secret.")], async_req: Optional[bool]=None, **kwargs) -> Union[IssueStatelessChannelAccessTokenResponse, Awaitable[IssueStatelessChannelAccessTokenResponse]]: # noqa: E501
571571
"""issue_stateless_channel_token # noqa: E501
572572
573+
.. deprecated::
574+
Use :func:`issue_stateless_channel_token_by_jwt_assertion` or
575+
:func:`issue_stateless_channel_token_by_client_secret` instead.
576+
573577
Issues a new stateless channel access token, which doesn't have max active token limit unlike the other token types. The newly issued token is only valid for 15 minutes but can not be revoked until it naturally expires. # noqa: E501
574578
This method makes a synchronous HTTP request by default. To make an
575579
asynchronous HTTP request, please pass async_req=True
@@ -609,6 +613,10 @@ def issue_stateless_channel_token(self, grant_type : Annotated[StrictStr, Field(
609613
def issue_stateless_channel_token_with_http_info(self, grant_type : Annotated[StrictStr, Field(..., description="`client_credentials`")], client_assertion_type : Annotated[StrictStr, Field(..., description="URL-encoded value of `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`")], client_assertion : Annotated[StrictStr, Field(..., description="A JSON Web Token the client needs to create and sign with the private key of the Assertion Signing Key.")], client_id : Annotated[StrictStr, Field(..., description="Channel ID.")], client_secret : Annotated[StrictStr, Field(..., description="Channel secret.")], **kwargs) -> ApiResponse: # noqa: E501
610614
"""issue_stateless_channel_token # noqa: E501
611615
616+
.. deprecated::
617+
Use :func:`issue_stateless_channel_token_with_http_info_by_jwt_assertion` or
618+
:func:`issue_stateless_channel_token_with_http_info_by_client_secret` instead.
619+
612620
Issues a new stateless channel access token, which doesn't have max active token limit unlike the other token types. The newly issued token is only valid for 15 minutes but can not be revoked until it naturally expires. # noqa: E501
613621
This method makes a synchronous HTTP request by default. To make an
614622
asynchronous HTTP request, please pass async_req=True
@@ -1376,3 +1384,69 @@ def verify_channel_token_by_jwt_with_http_info(self, access_token : Annotated[St
13761384
_host=_host,
13771385
collection_formats=_collection_formats,
13781386
_request_auth=_params.get('_request_auth'))
1387+
1388+
def issue_stateless_channel_token_by_jwt_assertion(self, client_assertion, **kwargs):
1389+
"""Issue a stateless channel access token using a JSON Web Token (JWT).
1390+
1391+
:param str client_assertion: A JSON Web Token the client needs to create and sign with the private key of the Assertion Signing Key.
1392+
:return: Returns the result object.
1393+
:rtype: IssueStatelessChannelAccessTokenResponse
1394+
"""
1395+
return self.issue_stateless_channel_token(
1396+
grant_type='client_credentials',
1397+
client_assertion_type='urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
1398+
client_assertion=client_assertion,
1399+
client_id='',
1400+
client_secret='',
1401+
**kwargs,
1402+
)
1403+
1404+
def issue_stateless_channel_token_by_client_secret(self, client_id, client_secret, **kwargs):
1405+
"""Issue a stateless channel access token using client ID and client secret.
1406+
1407+
:param str client_id: Channel ID.
1408+
:param str client_secret: Channel secret.
1409+
:return: Returns the result object.
1410+
:rtype: IssueStatelessChannelAccessTokenResponse
1411+
"""
1412+
return self.issue_stateless_channel_token(
1413+
grant_type='client_credentials',
1414+
client_assertion_type='',
1415+
client_assertion='',
1416+
client_id=client_id,
1417+
client_secret=client_secret,
1418+
**kwargs,
1419+
)
1420+
1421+
def issue_stateless_channel_token_with_http_info_by_jwt_assertion(self, client_assertion, **kwargs):
1422+
"""Issue a stateless channel access token using a JSON Web Token (JWT).
1423+
1424+
:param str client_assertion: A JSON Web Token the client needs to create and sign with the private key of the Assertion Signing Key.
1425+
:return: Returns the result object.
1426+
:rtype: ApiResponse
1427+
"""
1428+
return self.issue_stateless_channel_token_with_http_info(
1429+
grant_type='client_credentials',
1430+
client_assertion_type='urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
1431+
client_assertion=client_assertion,
1432+
client_id='',
1433+
client_secret='',
1434+
**kwargs,
1435+
)
1436+
1437+
def issue_stateless_channel_token_with_http_info_by_client_secret(self, client_id, client_secret, **kwargs):
1438+
"""Issue a stateless channel access token using client ID and client secret.
1439+
1440+
:param str client_id: Channel ID.
1441+
:param str client_secret: Channel secret.
1442+
:return: Returns the result object.
1443+
:rtype: ApiResponse
1444+
"""
1445+
return self.issue_stateless_channel_token_with_http_info(
1446+
grant_type='client_credentials',
1447+
client_assertion_type='',
1448+
client_assertion='',
1449+
client_id=client_id,
1450+
client_secret=client_secret,
1451+
**kwargs,
1452+
)

linebot/v3/oauth/api/channel_access_token.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,10 @@ def issue_channel_token_by_jwt_with_http_info(self, grant_type : Annotated[Stric
530530
def issue_stateless_channel_token(self, grant_type : Annotated[StrictStr, Field(..., description="`client_credentials`")], client_assertion_type : Annotated[StrictStr, Field(..., description="URL-encoded value of `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`")], client_assertion : Annotated[StrictStr, Field(..., description="A JSON Web Token the client needs to create and sign with the private key of the Assertion Signing Key.")], client_id : Annotated[StrictStr, Field(..., description="Channel ID.")], client_secret : Annotated[StrictStr, Field(..., description="Channel secret.")], **kwargs) -> IssueStatelessChannelAccessTokenResponse: # noqa: E501
531531
"""issue_stateless_channel_token # noqa: E501
532532
533+
.. deprecated::
534+
Use :func:`issue_stateless_channel_token_by_jwt_assertion` or
535+
:func:`issue_stateless_channel_token_by_client_secret` instead.
536+
533537
Issues a new stateless channel access token, which doesn't have max active token limit unlike the other token types. The newly issued token is only valid for 15 minutes but can not be revoked until it naturally expires. # noqa: E501
534538
This method makes a synchronous HTTP request by default. To make an
535539
asynchronous HTTP request, please pass async_req=True
@@ -567,6 +571,10 @@ def issue_stateless_channel_token(self, grant_type : Annotated[StrictStr, Field(
567571
def issue_stateless_channel_token_with_http_info(self, grant_type : Annotated[StrictStr, Field(..., description="`client_credentials`")], client_assertion_type : Annotated[StrictStr, Field(..., description="URL-encoded value of `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`")], client_assertion : Annotated[StrictStr, Field(..., description="A JSON Web Token the client needs to create and sign with the private key of the Assertion Signing Key.")], client_id : Annotated[StrictStr, Field(..., description="Channel ID.")], client_secret : Annotated[StrictStr, Field(..., description="Channel secret.")], **kwargs) -> ApiResponse: # noqa: E501
568572
"""issue_stateless_channel_token # noqa: E501
569573
574+
.. deprecated::
575+
Use :func:`issue_stateless_channel_token_with_http_info_by_jwt_assertion` or
576+
:func:`issue_stateless_channel_token_with_http_info_by_client_secret` instead.
577+
570578
Issues a new stateless channel access token, which doesn't have max active token limit unlike the other token types. The newly issued token is only valid for 15 minutes but can not be revoked until it naturally expires. # noqa: E501
571579
This method makes a synchronous HTTP request by default. To make an
572580
asynchronous HTTP request, please pass async_req=True
@@ -1294,3 +1302,69 @@ def verify_channel_token_by_jwt_with_http_info(self, access_token : Annotated[St
12941302
_host=_host,
12951303
collection_formats=_collection_formats,
12961304
_request_auth=_params.get('_request_auth'))
1305+
1306+
def issue_stateless_channel_token_by_jwt_assertion(self, client_assertion, **kwargs):
1307+
"""Issue a stateless channel access token using a JSON Web Token (JWT).
1308+
1309+
:param str client_assertion: A JSON Web Token the client needs to create and sign with the private key of the Assertion Signing Key.
1310+
:return: Returns the result object.
1311+
:rtype: IssueStatelessChannelAccessTokenResponse
1312+
"""
1313+
return self.issue_stateless_channel_token(
1314+
grant_type='client_credentials',
1315+
client_assertion_type='urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
1316+
client_assertion=client_assertion,
1317+
client_id='',
1318+
client_secret='',
1319+
**kwargs,
1320+
)
1321+
1322+
def issue_stateless_channel_token_by_client_secret(self, client_id, client_secret, **kwargs):
1323+
"""Issue a stateless channel access token using client ID and client secret.
1324+
1325+
:param str client_id: Channel ID.
1326+
:param str client_secret: Channel secret.
1327+
:return: Returns the result object.
1328+
:rtype: IssueStatelessChannelAccessTokenResponse
1329+
"""
1330+
return self.issue_stateless_channel_token(
1331+
grant_type='client_credentials',
1332+
client_assertion_type='',
1333+
client_assertion='',
1334+
client_id=client_id,
1335+
client_secret=client_secret,
1336+
**kwargs,
1337+
)
1338+
1339+
def issue_stateless_channel_token_with_http_info_by_jwt_assertion(self, client_assertion, **kwargs):
1340+
"""Issue a stateless channel access token using a JSON Web Token (JWT).
1341+
1342+
:param str client_assertion: A JSON Web Token the client needs to create and sign with the private key of the Assertion Signing Key.
1343+
:return: Returns the result object.
1344+
:rtype: ApiResponse
1345+
"""
1346+
return self.issue_stateless_channel_token_with_http_info(
1347+
grant_type='client_credentials',
1348+
client_assertion_type='urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
1349+
client_assertion=client_assertion,
1350+
client_id='',
1351+
client_secret='',
1352+
**kwargs,
1353+
)
1354+
1355+
def issue_stateless_channel_token_with_http_info_by_client_secret(self, client_id, client_secret, **kwargs):
1356+
"""Issue a stateless channel access token using client ID and client secret.
1357+
1358+
:param str client_id: Channel ID.
1359+
:param str client_secret: Channel secret.
1360+
:return: Returns the result object.
1361+
:rtype: ApiResponse
1362+
"""
1363+
return self.issue_stateless_channel_token_with_http_info(
1364+
grant_type='client_credentials',
1365+
client_assertion_type='',
1366+
client_assertion='',
1367+
client_id=client_id,
1368+
client_secret=client_secret,
1369+
**kwargs,
1370+
)

0 commit comments

Comments
 (0)