Skip to content

Commit 21cf83f

Browse files
committed
Update protocol. Support SIP inbound/outbound trunks, grants.
1 parent cf1dc64 commit 21cf83f

22 files changed

+847
-526
lines changed

livekit-api/livekit/api/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,6 @@
2525

2626
from .twirp_client import TwirpError, TwirpErrorCode
2727
from .livekit_api import LiveKitAPI
28-
from .access_token import VideoGrants, AccessToken, TokenVerifier
28+
from .access_token import VideoGrants, SIPGrants, AccessToken, TokenVerifier
2929
from .webhook import WebhookReceiver
3030
from .version import __version__

livekit-api/livekit/api/_service.py

+9-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import aiohttp
33
from abc import ABC
44
from .twirp_client import TwirpClient
5-
from .access_token import AccessToken, VideoGrants
5+
from .access_token import AccessToken, VideoGrants, SIPGrants
66

77
AUTHORIZATION = "authorization"
88

@@ -15,8 +15,14 @@ def __init__(
1515
self.api_key = api_key
1616
self.api_secret = api_secret
1717

18-
def _auth_header(self, grants: VideoGrants) -> Dict[str, str]:
19-
token = AccessToken(self.api_key, self.api_secret).with_grants(grants).to_jwt()
18+
def _auth_header(
19+
self, grants: VideoGrants, sip: SIPGrants | None = None
20+
) -> Dict[str, str]:
21+
tok = AccessToken(self.api_key, self.api_secret).with_grants(grants)
22+
if sip is not None:
23+
tok = tok.with_sip_grants(sip)
24+
25+
token = tok.to_jwt()
2026

2127
headers = {}
2228
headers[AUTHORIZATION] = "Bearer {}".format(token)

livekit-api/livekit/api/access_token.py

+21
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,20 @@ class VideoGrants:
6363
agent: bool = False
6464

6565

66+
@dataclasses.dataclass
67+
class SIPGrants:
68+
# manage sip resources
69+
admin: bool = False
70+
# make outbound calls
71+
call: bool = False
72+
73+
6674
@dataclasses.dataclass
6775
class Claims:
6876
identity: str = ""
6977
name: str = ""
7078
video: VideoGrants = dataclasses.field(default_factory=VideoGrants)
79+
sip: SIPGrants = dataclasses.field(default_factory=SIPGrants)
7180
metadata: str = ""
7281
sha256: str = ""
7382

@@ -100,6 +109,10 @@ def with_grants(self, grants: VideoGrants) -> "AccessToken":
100109
self.claims.video = grants
101110
return self
102111

112+
def with_sip_grants(self, grants: SIPGrants) -> "AccessToken":
113+
self.claims.sip = grants
114+
return self
115+
103116
def with_identity(self, identity: str) -> "AccessToken":
104117
self.identity = identity
105118
return self
@@ -173,10 +186,18 @@ def verify(self, token: str) -> Claims:
173186
}
174187
video = VideoGrants(**video_dict)
175188

189+
sip_dict = claims.get("sip", dict())
190+
sip_dict = {camel_to_snake(k): v for k, v in sip_dict.items()}
191+
sip_dict = {
192+
k: v for k, v in sip_dict.items() if k in SIPGrants.__dataclass_fields__
193+
}
194+
sip = SIPGrants(**sip_dict)
195+
176196
return Claims(
177197
identity=claims.get("sub", ""),
178198
name=claims.get("name", ""),
179199
video=video,
200+
sip=sip,
180201
metadata=claims.get("metadata", ""),
181202
sha256=claims.get("sha256", ""),
182203
)

livekit-api/livekit/api/sip_service.py

+52-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import aiohttp
22
from livekit.protocol import sip as proto_sip
33
from ._service import Service
4-
from .access_token import VideoGrants
4+
from .access_token import VideoGrants, SIPGrants
55

66
SVC = "SIP"
77

@@ -19,29 +19,73 @@ async def create_sip_trunk(
1919
SVC,
2020
"CreateSIPTrunk",
2121
create,
22-
self._auth_header(VideoGrants()),
22+
self._auth_header(VideoGrants(), sip=SIPGrants(admin=True)),
2323
proto_sip.SIPTrunkInfo,
2424
)
2525

26+
async def create_sip_inbound_trunk(
27+
self, create: proto_sip.CreateSIPInboundTrunkRequest
28+
) -> proto_sip.SIPInboundTrunkInfo:
29+
return await self._client.request(
30+
SVC,
31+
"CreateSIPInboundTrunk",
32+
create,
33+
self._auth_header(VideoGrants(), sip=SIPGrants(admin=True)),
34+
proto_sip.SIPInboundTrunkInfo,
35+
)
36+
37+
async def create_sip_outbound_trunk(
38+
self, create: proto_sip.CreateSIPOutboundTrunkRequest
39+
) -> proto_sip.SIPOutboundTrunkInfo:
40+
return await self._client.request(
41+
SVC,
42+
"CreateSIPOutboundTrunk",
43+
create,
44+
self._auth_header(VideoGrants(), sip=SIPGrants(admin=True)),
45+
proto_sip.SIPOutboundTrunkInfo,
46+
)
47+
2648
async def list_sip_trunk(
2749
self, list: proto_sip.ListSIPTrunkRequest
2850
) -> proto_sip.ListSIPTrunkResponse:
2951
return await self._client.request(
3052
SVC,
3153
"ListSIPTrunk",
3254
list,
33-
self._auth_header(VideoGrants()),
55+
self._auth_header(VideoGrants(), sip=SIPGrants(admin=True)),
3456
proto_sip.ListSIPTrunkResponse,
3557
)
3658

59+
async def list_sip_inbound_trunk(
60+
self, list: proto_sip.ListSIPInboundTrunkRequest
61+
) -> proto_sip.ListSIPInboundTrunkResponse:
62+
return await self._client.request(
63+
SVC,
64+
"ListSIPInboundTrunk",
65+
list,
66+
self._auth_header(VideoGrants(), sip=SIPGrants(admin=True)),
67+
proto_sip.ListSIPInboundTrunkResponse,
68+
)
69+
70+
async def list_sip_outbound_trunk(
71+
self, list: proto_sip.ListSIPOutboundTrunkRequest
72+
) -> proto_sip.ListSIPOutboundTrunkResponse:
73+
return await self._client.request(
74+
SVC,
75+
"ListSIPOutboundTrunk",
76+
list,
77+
self._auth_header(VideoGrants(), sip=SIPGrants(admin=True)),
78+
proto_sip.ListSIPOutboundTrunkResponse,
79+
)
80+
3781
async def delete_sip_trunk(
3882
self, delete: proto_sip.DeleteSIPTrunkRequest
3983
) -> proto_sip.SIPTrunkInfo:
4084
return await self._client.request(
4185
SVC,
4286
"DeleteSIPTrunk",
4387
delete,
44-
self._auth_header(VideoGrants()),
88+
self._auth_header(VideoGrants(), sip=SIPGrants(admin=True)),
4589
proto_sip.SIPTrunkInfo,
4690
)
4791

@@ -52,7 +96,7 @@ async def create_sip_dispatch_rule(
5296
SVC,
5397
"CreateSIPDispatchRule",
5498
create,
55-
self._auth_header(VideoGrants()),
99+
self._auth_header(VideoGrants(), sip=SIPGrants(admin=True)),
56100
proto_sip.SIPDispatchRuleInfo,
57101
)
58102

@@ -63,7 +107,7 @@ async def list_sip_dispatch_rule(
63107
SVC,
64108
"ListSIPDispatchRule",
65109
list,
66-
self._auth_header(VideoGrants()),
110+
self._auth_header(VideoGrants(), sip=SIPGrants(admin=True)),
67111
proto_sip.ListSIPDispatchRuleResponse,
68112
)
69113

@@ -74,7 +118,7 @@ async def delete_sip_dispatch_rule(
74118
SVC,
75119
"DeleteSIPDispatchRule",
76120
delete,
77-
self._auth_header(VideoGrants()),
121+
self._auth_header(VideoGrants(), sip=SIPGrants(admin=True)),
78122
proto_sip.SIPDispatchRuleInfo,
79123
)
80124

@@ -85,6 +129,6 @@ async def create_sip_participant(
85129
SVC,
86130
"CreateSIPParticipant",
87131
create,
88-
self._auth_header(VideoGrants()),
132+
self._auth_header(VideoGrants(), sip=SIPGrants(call=True)),
89133
proto_sip.SIPParticipantInfo,
90134
)

livekit-api/tests/test_access_token.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
import datetime
22

33
import pytest # type: ignore
4-
from livekit.api import AccessToken, TokenVerifier, VideoGrants
4+
from livekit.api import AccessToken, TokenVerifier, VideoGrants, SIPGrants
55

66
TEST_API_KEY = "myapikey"
77
TEST_API_SECRET = "thiskeyistotallyunsafe"
88

99

1010
def test_verify_token():
1111
grants = VideoGrants(room_join=True, room="test_room")
12+
sip = SIPGrants(admin=True)
1213

1314
token = (
1415
AccessToken(TEST_API_KEY, TEST_API_SECRET)
1516
.with_identity("test_identity")
1617
.with_metadata("test_metadata")
1718
.with_grants(grants)
19+
.with_sip_grants(sip)
1820
.to_jwt()
1921
)
2022

@@ -24,6 +26,7 @@ def test_verify_token():
2426
assert claims.identity == "test_identity"
2527
assert claims.metadata == "test_metadata"
2628
assert claims.video == grants
29+
assert claims.sip == sip
2730

2831

2932
def test_verify_token_invalid():

livekit-protocol/livekit/protocol/agent.py

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)