flwr-nightly 1.14.0.dev20241208__py3-none-any.whl → 1.14.0.dev20241210__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of flwr-nightly might be problematic. Click here for more details.

@@ -0,0 +1,24 @@
1
+ # Copyright 2024 Flower Labs GmbH. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+ """Auth plugin components."""
16
+
17
+
18
+ from .auth_plugin import CliAuthPlugin as CliAuthPlugin
19
+ from .auth_plugin import ExecAuthPlugin as ExecAuthPlugin
20
+
21
+ __all__ = [
22
+ "CliAuthPlugin",
23
+ "ExecAuthPlugin",
24
+ ]
@@ -0,0 +1,111 @@
1
+ # Copyright 2024 Flower Labs GmbH. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+ """Abstract classes for Flower User Auth Plugin."""
16
+
17
+
18
+ from abc import ABC, abstractmethod
19
+ from collections.abc import Sequence
20
+ from pathlib import Path
21
+ from typing import Any, Optional, Union
22
+
23
+ from flwr.proto.exec_pb2_grpc import ExecStub
24
+
25
+
26
+ class ExecAuthPlugin(ABC):
27
+ """Abstract Flower Auth Plugin class for ExecServicer.
28
+
29
+ Parameters
30
+ ----------
31
+ config : dict[str, Any]
32
+ The authentication configuration loaded from a YAML file.
33
+ """
34
+
35
+ @abstractmethod
36
+ def __init__(self, config: dict[str, Any]):
37
+ """Abstract constructor."""
38
+
39
+ @abstractmethod
40
+ def get_login_details(self) -> dict[str, str]:
41
+ """Get the login details."""
42
+
43
+ @abstractmethod
44
+ def validate_tokens_in_metadata(
45
+ self, metadata: Sequence[tuple[str, Union[str, bytes]]]
46
+ ) -> bool:
47
+ """Validate authentication tokens in the provided metadata."""
48
+
49
+ @abstractmethod
50
+ def get_auth_tokens(self, auth_details: dict[str, str]) -> dict[str, str]:
51
+ """Get authentication tokens."""
52
+
53
+ @abstractmethod
54
+ def refresh_tokens(
55
+ self, metadata: Sequence[tuple[str, Union[str, bytes]]]
56
+ ) -> Optional[Sequence[tuple[str, Union[str, bytes]]]]:
57
+ """Refresh authentication tokens in the provided metadata."""
58
+
59
+
60
+ class CliAuthPlugin(ABC):
61
+ """Abstract Flower Auth Plugin class for CLI.
62
+
63
+ Parameters
64
+ ----------
65
+ user_auth_config_path : Path
66
+ The path to the user's authentication configuration file.
67
+ """
68
+
69
+ @staticmethod
70
+ @abstractmethod
71
+ def login(
72
+ login_details: dict[str, str],
73
+ exec_stub: ExecStub,
74
+ ) -> dict[str, Any]:
75
+ """Authenticate the user with the SuperLink.
76
+
77
+ Parameters
78
+ ----------
79
+ login_details : dict[str, str]
80
+ A dictionary containing the user's login details.
81
+ exec_stub : ExecStub
82
+ An instance of `ExecStub` used for communication with the SuperLink.
83
+
84
+ Returns
85
+ -------
86
+ user_auth_config : dict[str, Any]
87
+ A dictionary containing the user's authentication configuration
88
+ in JSON format.
89
+ """
90
+
91
+ @abstractmethod
92
+ def __init__(self, user_auth_config_path: Path):
93
+ """Abstract constructor."""
94
+
95
+ @abstractmethod
96
+ def store_tokens(self, user_auth_config: dict[str, Any]) -> None:
97
+ """Store authentication tokens from the provided user_auth_config.
98
+
99
+ The configuration, including tokens, will be saved as a JSON file
100
+ at `user_auth_config_path`.
101
+ """
102
+
103
+ @abstractmethod
104
+ def load_tokens(self) -> None:
105
+ """Load authentication tokens from the user_auth_config_path."""
106
+
107
+ @abstractmethod
108
+ def write_tokens_to_metadata(
109
+ self, metadata: Sequence[tuple[str, Union[str, bytes]]]
110
+ ) -> Sequence[tuple[str, Union[str, bytes]]]:
111
+ """Write authentication tokens to the provided metadata."""
flwr/common/constant.py CHANGED
@@ -110,6 +110,8 @@ LOG_UPLOAD_INTERVAL = 0.2 # Minimum interval between two log uploads
110
110
  # Retry configurations
111
111
  MAX_RETRY_DELAY = 20 # Maximum delay duration between two consecutive retries.
112
112
 
113
+ AUTH_TYPE = "auth_type"
114
+
113
115
 
114
116
  class MessageType:
115
117
  """Message type."""
flwr/proto/exec_pb2.py CHANGED
@@ -18,7 +18,7 @@ from flwr.proto import recordset_pb2 as flwr_dot_proto_dot_recordset__pb2
18
18
  from flwr.proto import run_pb2 as flwr_dot_proto_dot_run__pb2
19
19
 
20
20
 
21
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x66lwr/proto/exec.proto\x12\nflwr.proto\x1a\x14\x66lwr/proto/fab.proto\x1a\x1a\x66lwr/proto/transport.proto\x1a\x1a\x66lwr/proto/recordset.proto\x1a\x14\x66lwr/proto/run.proto\"\xfb\x01\n\x0fStartRunRequest\x12\x1c\n\x03\x66\x61\x62\x18\x01 \x01(\x0b\x32\x0f.flwr.proto.Fab\x12H\n\x0foverride_config\x18\x02 \x03(\x0b\x32/.flwr.proto.StartRunRequest.OverrideConfigEntry\x12\x35\n\x12\x66\x65\x64\x65ration_options\x18\x03 \x01(\x0b\x32\x19.flwr.proto.ConfigsRecord\x1aI\n\x13OverrideConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.flwr.proto.Scalar:\x02\x38\x01\"2\n\x10StartRunResponse\x12\x13\n\x06run_id\x18\x01 \x01(\x04H\x00\x88\x01\x01\x42\t\n\x07_run_id\"<\n\x11StreamLogsRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\x12\x17\n\x0f\x61\x66ter_timestamp\x18\x02 \x01(\x01\"B\n\x12StreamLogsResponse\x12\x12\n\nlog_output\x18\x01 \x01(\t\x12\x18\n\x10latest_timestamp\x18\x02 \x01(\x01\"1\n\x0fListRunsRequest\x12\x13\n\x06run_id\x18\x01 \x01(\x04H\x00\x88\x01\x01\x42\t\n\x07_run_id\"\x9d\x01\n\x10ListRunsResponse\x12;\n\x08run_dict\x18\x01 \x03(\x0b\x32).flwr.proto.ListRunsResponse.RunDictEntry\x12\x0b\n\x03now\x18\x02 \x01(\t\x1a?\n\x0cRunDictEntry\x12\x0b\n\x03key\x18\x01 \x01(\x04\x12\x1e\n\x05value\x18\x02 \x01(\x0b\x32\x0f.flwr.proto.Run:\x02\x38\x01\" \n\x0eStopRunRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\"\"\n\x0fStopRunResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x32\xaf\x02\n\x04\x45xec\x12G\n\x08StartRun\x12\x1b.flwr.proto.StartRunRequest\x1a\x1c.flwr.proto.StartRunResponse\"\x00\x12\x44\n\x07StopRun\x12\x1a.flwr.proto.StopRunRequest\x1a\x1b.flwr.proto.StopRunResponse\"\x00\x12O\n\nStreamLogs\x12\x1d.flwr.proto.StreamLogsRequest\x1a\x1e.flwr.proto.StreamLogsResponse\"\x00\x30\x01\x12G\n\x08ListRuns\x12\x1b.flwr.proto.ListRunsRequest\x1a\x1c.flwr.proto.ListRunsResponse\"\x00\x62\x06proto3')
21
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x66lwr/proto/exec.proto\x12\nflwr.proto\x1a\x14\x66lwr/proto/fab.proto\x1a\x1a\x66lwr/proto/transport.proto\x1a\x1a\x66lwr/proto/recordset.proto\x1a\x14\x66lwr/proto/run.proto\"\xfb\x01\n\x0fStartRunRequest\x12\x1c\n\x03\x66\x61\x62\x18\x01 \x01(\x0b\x32\x0f.flwr.proto.Fab\x12H\n\x0foverride_config\x18\x02 \x03(\x0b\x32/.flwr.proto.StartRunRequest.OverrideConfigEntry\x12\x35\n\x12\x66\x65\x64\x65ration_options\x18\x03 \x01(\x0b\x32\x19.flwr.proto.ConfigsRecord\x1aI\n\x13OverrideConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.flwr.proto.Scalar:\x02\x38\x01\"2\n\x10StartRunResponse\x12\x13\n\x06run_id\x18\x01 \x01(\x04H\x00\x88\x01\x01\x42\t\n\x07_run_id\"<\n\x11StreamLogsRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\x12\x17\n\x0f\x61\x66ter_timestamp\x18\x02 \x01(\x01\"B\n\x12StreamLogsResponse\x12\x12\n\nlog_output\x18\x01 \x01(\t\x12\x18\n\x10latest_timestamp\x18\x02 \x01(\x01\"1\n\x0fListRunsRequest\x12\x13\n\x06run_id\x18\x01 \x01(\x04H\x00\x88\x01\x01\x42\t\n\x07_run_id\"\x9d\x01\n\x10ListRunsResponse\x12;\n\x08run_dict\x18\x01 \x03(\x0b\x32).flwr.proto.ListRunsResponse.RunDictEntry\x12\x0b\n\x03now\x18\x02 \x01(\t\x1a?\n\x0cRunDictEntry\x12\x0b\n\x03key\x18\x01 \x01(\x04\x12\x1e\n\x05value\x18\x02 \x01(\x0b\x32\x0f.flwr.proto.Run:\x02\x38\x01\"\x18\n\x16GetLoginDetailsRequest\"\x9c\x01\n\x17GetLoginDetailsResponse\x12L\n\rlogin_details\x18\x01 \x03(\x0b\x32\x35.flwr.proto.GetLoginDetailsResponse.LoginDetailsEntry\x1a\x33\n\x11LoginDetailsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x93\x01\n\x14GetAuthTokensRequest\x12G\n\x0c\x61uth_details\x18\x01 \x03(\x0b\x32\x31.flwr.proto.GetAuthTokensRequest.AuthDetailsEntry\x1a\x32\n\x10\x41uthDetailsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x92\x01\n\x15GetAuthTokensResponse\x12\x46\n\x0b\x61uth_tokens\x18\x01 \x03(\x0b\x32\x31.flwr.proto.GetAuthTokensResponse.AuthTokensEntry\x1a\x31\n\x0f\x41uthTokensEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\" \n\x0eStopRunRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\"\"\n\x0fStopRunResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x32\xe5\x03\n\x04\x45xec\x12G\n\x08StartRun\x12\x1b.flwr.proto.StartRunRequest\x1a\x1c.flwr.proto.StartRunResponse\"\x00\x12\x44\n\x07StopRun\x12\x1a.flwr.proto.StopRunRequest\x1a\x1b.flwr.proto.StopRunResponse\"\x00\x12O\n\nStreamLogs\x12\x1d.flwr.proto.StreamLogsRequest\x1a\x1e.flwr.proto.StreamLogsResponse\"\x00\x30\x01\x12G\n\x08ListRuns\x12\x1b.flwr.proto.ListRunsRequest\x1a\x1c.flwr.proto.ListRunsResponse\"\x00\x12\\\n\x0fGetLoginDetails\x12\".flwr.proto.GetLoginDetailsRequest\x1a#.flwr.proto.GetLoginDetailsResponse\"\x00\x12V\n\rGetAuthTokens\x12 .flwr.proto.GetAuthTokensRequest\x1a!.flwr.proto.GetAuthTokensResponse\"\x00\x62\x06proto3')
22
22
 
23
23
  _globals = globals()
24
24
  _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -29,6 +29,12 @@ if _descriptor._USE_C_DESCRIPTORS == False:
29
29
  _globals['_STARTRUNREQUEST_OVERRIDECONFIGENTRY']._serialized_options = b'8\001'
30
30
  _globals['_LISTRUNSRESPONSE_RUNDICTENTRY']._options = None
31
31
  _globals['_LISTRUNSRESPONSE_RUNDICTENTRY']._serialized_options = b'8\001'
32
+ _globals['_GETLOGINDETAILSRESPONSE_LOGINDETAILSENTRY']._options = None
33
+ _globals['_GETLOGINDETAILSRESPONSE_LOGINDETAILSENTRY']._serialized_options = b'8\001'
34
+ _globals['_GETAUTHTOKENSREQUEST_AUTHDETAILSENTRY']._options = None
35
+ _globals['_GETAUTHTOKENSREQUEST_AUTHDETAILSENTRY']._serialized_options = b'8\001'
36
+ _globals['_GETAUTHTOKENSRESPONSE_AUTHTOKENSENTRY']._options = None
37
+ _globals['_GETAUTHTOKENSRESPONSE_AUTHTOKENSENTRY']._serialized_options = b'8\001'
32
38
  _globals['_STARTRUNREQUEST']._serialized_start=138
33
39
  _globals['_STARTRUNREQUEST']._serialized_end=389
34
40
  _globals['_STARTRUNREQUEST_OVERRIDECONFIGENTRY']._serialized_start=316
@@ -45,10 +51,24 @@ if _descriptor._USE_C_DESCRIPTORS == False:
45
51
  _globals['_LISTRUNSRESPONSE']._serialized_end=782
46
52
  _globals['_LISTRUNSRESPONSE_RUNDICTENTRY']._serialized_start=719
47
53
  _globals['_LISTRUNSRESPONSE_RUNDICTENTRY']._serialized_end=782
48
- _globals['_STOPRUNREQUEST']._serialized_start=784
49
- _globals['_STOPRUNREQUEST']._serialized_end=816
50
- _globals['_STOPRUNRESPONSE']._serialized_start=818
51
- _globals['_STOPRUNRESPONSE']._serialized_end=852
52
- _globals['_EXEC']._serialized_start=855
53
- _globals['_EXEC']._serialized_end=1158
54
+ _globals['_GETLOGINDETAILSREQUEST']._serialized_start=784
55
+ _globals['_GETLOGINDETAILSREQUEST']._serialized_end=808
56
+ _globals['_GETLOGINDETAILSRESPONSE']._serialized_start=811
57
+ _globals['_GETLOGINDETAILSRESPONSE']._serialized_end=967
58
+ _globals['_GETLOGINDETAILSRESPONSE_LOGINDETAILSENTRY']._serialized_start=916
59
+ _globals['_GETLOGINDETAILSRESPONSE_LOGINDETAILSENTRY']._serialized_end=967
60
+ _globals['_GETAUTHTOKENSREQUEST']._serialized_start=970
61
+ _globals['_GETAUTHTOKENSREQUEST']._serialized_end=1117
62
+ _globals['_GETAUTHTOKENSREQUEST_AUTHDETAILSENTRY']._serialized_start=1067
63
+ _globals['_GETAUTHTOKENSREQUEST_AUTHDETAILSENTRY']._serialized_end=1117
64
+ _globals['_GETAUTHTOKENSRESPONSE']._serialized_start=1120
65
+ _globals['_GETAUTHTOKENSRESPONSE']._serialized_end=1266
66
+ _globals['_GETAUTHTOKENSRESPONSE_AUTHTOKENSENTRY']._serialized_start=1217
67
+ _globals['_GETAUTHTOKENSRESPONSE_AUTHTOKENSENTRY']._serialized_end=1266
68
+ _globals['_STOPRUNREQUEST']._serialized_start=1268
69
+ _globals['_STOPRUNREQUEST']._serialized_end=1300
70
+ _globals['_STOPRUNRESPONSE']._serialized_start=1302
71
+ _globals['_STOPRUNRESPONSE']._serialized_end=1336
72
+ _globals['_EXEC']._serialized_start=1339
73
+ _globals['_EXEC']._serialized_end=1824
54
74
  # @@protoc_insertion_point(module_scope)
flwr/proto/exec_pb2.pyi CHANGED
@@ -135,6 +135,87 @@ class ListRunsResponse(google.protobuf.message.Message):
135
135
  def ClearField(self, field_name: typing_extensions.Literal["now",b"now","run_dict",b"run_dict"]) -> None: ...
136
136
  global___ListRunsResponse = ListRunsResponse
137
137
 
138
+ class GetLoginDetailsRequest(google.protobuf.message.Message):
139
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
140
+ def __init__(self,
141
+ ) -> None: ...
142
+ global___GetLoginDetailsRequest = GetLoginDetailsRequest
143
+
144
+ class GetLoginDetailsResponse(google.protobuf.message.Message):
145
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
146
+ class LoginDetailsEntry(google.protobuf.message.Message):
147
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
148
+ KEY_FIELD_NUMBER: builtins.int
149
+ VALUE_FIELD_NUMBER: builtins.int
150
+ key: typing.Text
151
+ value: typing.Text
152
+ def __init__(self,
153
+ *,
154
+ key: typing.Text = ...,
155
+ value: typing.Text = ...,
156
+ ) -> None: ...
157
+ def ClearField(self, field_name: typing_extensions.Literal["key",b"key","value",b"value"]) -> None: ...
158
+
159
+ LOGIN_DETAILS_FIELD_NUMBER: builtins.int
160
+ @property
161
+ def login_details(self) -> google.protobuf.internal.containers.ScalarMap[typing.Text, typing.Text]: ...
162
+ def __init__(self,
163
+ *,
164
+ login_details: typing.Optional[typing.Mapping[typing.Text, typing.Text]] = ...,
165
+ ) -> None: ...
166
+ def ClearField(self, field_name: typing_extensions.Literal["login_details",b"login_details"]) -> None: ...
167
+ global___GetLoginDetailsResponse = GetLoginDetailsResponse
168
+
169
+ class GetAuthTokensRequest(google.protobuf.message.Message):
170
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
171
+ class AuthDetailsEntry(google.protobuf.message.Message):
172
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
173
+ KEY_FIELD_NUMBER: builtins.int
174
+ VALUE_FIELD_NUMBER: builtins.int
175
+ key: typing.Text
176
+ value: typing.Text
177
+ def __init__(self,
178
+ *,
179
+ key: typing.Text = ...,
180
+ value: typing.Text = ...,
181
+ ) -> None: ...
182
+ def ClearField(self, field_name: typing_extensions.Literal["key",b"key","value",b"value"]) -> None: ...
183
+
184
+ AUTH_DETAILS_FIELD_NUMBER: builtins.int
185
+ @property
186
+ def auth_details(self) -> google.protobuf.internal.containers.ScalarMap[typing.Text, typing.Text]: ...
187
+ def __init__(self,
188
+ *,
189
+ auth_details: typing.Optional[typing.Mapping[typing.Text, typing.Text]] = ...,
190
+ ) -> None: ...
191
+ def ClearField(self, field_name: typing_extensions.Literal["auth_details",b"auth_details"]) -> None: ...
192
+ global___GetAuthTokensRequest = GetAuthTokensRequest
193
+
194
+ class GetAuthTokensResponse(google.protobuf.message.Message):
195
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
196
+ class AuthTokensEntry(google.protobuf.message.Message):
197
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
198
+ KEY_FIELD_NUMBER: builtins.int
199
+ VALUE_FIELD_NUMBER: builtins.int
200
+ key: typing.Text
201
+ value: typing.Text
202
+ def __init__(self,
203
+ *,
204
+ key: typing.Text = ...,
205
+ value: typing.Text = ...,
206
+ ) -> None: ...
207
+ def ClearField(self, field_name: typing_extensions.Literal["key",b"key","value",b"value"]) -> None: ...
208
+
209
+ AUTH_TOKENS_FIELD_NUMBER: builtins.int
210
+ @property
211
+ def auth_tokens(self) -> google.protobuf.internal.containers.ScalarMap[typing.Text, typing.Text]: ...
212
+ def __init__(self,
213
+ *,
214
+ auth_tokens: typing.Optional[typing.Mapping[typing.Text, typing.Text]] = ...,
215
+ ) -> None: ...
216
+ def ClearField(self, field_name: typing_extensions.Literal["auth_tokens",b"auth_tokens"]) -> None: ...
217
+ global___GetAuthTokensResponse = GetAuthTokensResponse
218
+
138
219
  class StopRunRequest(google.protobuf.message.Message):
139
220
  DESCRIPTOR: google.protobuf.descriptor.Descriptor
140
221
  RUN_ID_FIELD_NUMBER: builtins.int
@@ -34,6 +34,16 @@ class ExecStub(object):
34
34
  request_serializer=flwr_dot_proto_dot_exec__pb2.ListRunsRequest.SerializeToString,
35
35
  response_deserializer=flwr_dot_proto_dot_exec__pb2.ListRunsResponse.FromString,
36
36
  )
37
+ self.GetLoginDetails = channel.unary_unary(
38
+ '/flwr.proto.Exec/GetLoginDetails',
39
+ request_serializer=flwr_dot_proto_dot_exec__pb2.GetLoginDetailsRequest.SerializeToString,
40
+ response_deserializer=flwr_dot_proto_dot_exec__pb2.GetLoginDetailsResponse.FromString,
41
+ )
42
+ self.GetAuthTokens = channel.unary_unary(
43
+ '/flwr.proto.Exec/GetAuthTokens',
44
+ request_serializer=flwr_dot_proto_dot_exec__pb2.GetAuthTokensRequest.SerializeToString,
45
+ response_deserializer=flwr_dot_proto_dot_exec__pb2.GetAuthTokensResponse.FromString,
46
+ )
37
47
 
38
48
 
39
49
  class ExecServicer(object):
@@ -67,6 +77,20 @@ class ExecServicer(object):
67
77
  context.set_details('Method not implemented!')
68
78
  raise NotImplementedError('Method not implemented!')
69
79
 
80
+ def GetLoginDetails(self, request, context):
81
+ """Get login details upon request
82
+ """
83
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
84
+ context.set_details('Method not implemented!')
85
+ raise NotImplementedError('Method not implemented!')
86
+
87
+ def GetAuthTokens(self, request, context):
88
+ """Get auth tokens upon request
89
+ """
90
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
91
+ context.set_details('Method not implemented!')
92
+ raise NotImplementedError('Method not implemented!')
93
+
70
94
 
71
95
  def add_ExecServicer_to_server(servicer, server):
72
96
  rpc_method_handlers = {
@@ -90,6 +114,16 @@ def add_ExecServicer_to_server(servicer, server):
90
114
  request_deserializer=flwr_dot_proto_dot_exec__pb2.ListRunsRequest.FromString,
91
115
  response_serializer=flwr_dot_proto_dot_exec__pb2.ListRunsResponse.SerializeToString,
92
116
  ),
117
+ 'GetLoginDetails': grpc.unary_unary_rpc_method_handler(
118
+ servicer.GetLoginDetails,
119
+ request_deserializer=flwr_dot_proto_dot_exec__pb2.GetLoginDetailsRequest.FromString,
120
+ response_serializer=flwr_dot_proto_dot_exec__pb2.GetLoginDetailsResponse.SerializeToString,
121
+ ),
122
+ 'GetAuthTokens': grpc.unary_unary_rpc_method_handler(
123
+ servicer.GetAuthTokens,
124
+ request_deserializer=flwr_dot_proto_dot_exec__pb2.GetAuthTokensRequest.FromString,
125
+ response_serializer=flwr_dot_proto_dot_exec__pb2.GetAuthTokensResponse.SerializeToString,
126
+ ),
93
127
  }
94
128
  generic_handler = grpc.method_handlers_generic_handler(
95
129
  'flwr.proto.Exec', rpc_method_handlers)
@@ -167,3 +201,37 @@ class Exec(object):
167
201
  flwr_dot_proto_dot_exec__pb2.ListRunsResponse.FromString,
168
202
  options, channel_credentials,
169
203
  insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
204
+
205
+ @staticmethod
206
+ def GetLoginDetails(request,
207
+ target,
208
+ options=(),
209
+ channel_credentials=None,
210
+ call_credentials=None,
211
+ insecure=False,
212
+ compression=None,
213
+ wait_for_ready=None,
214
+ timeout=None,
215
+ metadata=None):
216
+ return grpc.experimental.unary_unary(request, target, '/flwr.proto.Exec/GetLoginDetails',
217
+ flwr_dot_proto_dot_exec__pb2.GetLoginDetailsRequest.SerializeToString,
218
+ flwr_dot_proto_dot_exec__pb2.GetLoginDetailsResponse.FromString,
219
+ options, channel_credentials,
220
+ insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
221
+
222
+ @staticmethod
223
+ def GetAuthTokens(request,
224
+ target,
225
+ options=(),
226
+ channel_credentials=None,
227
+ call_credentials=None,
228
+ insecure=False,
229
+ compression=None,
230
+ wait_for_ready=None,
231
+ timeout=None,
232
+ metadata=None):
233
+ return grpc.experimental.unary_unary(request, target, '/flwr.proto.Exec/GetAuthTokens',
234
+ flwr_dot_proto_dot_exec__pb2.GetAuthTokensRequest.SerializeToString,
235
+ flwr_dot_proto_dot_exec__pb2.GetAuthTokensResponse.FromString,
236
+ options, channel_credentials,
237
+ insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
@@ -29,6 +29,16 @@ class ExecStub:
29
29
  flwr.proto.exec_pb2.ListRunsResponse]
30
30
  """flwr ls command"""
31
31
 
32
+ GetLoginDetails: grpc.UnaryUnaryMultiCallable[
33
+ flwr.proto.exec_pb2.GetLoginDetailsRequest,
34
+ flwr.proto.exec_pb2.GetLoginDetailsResponse]
35
+ """Get login details upon request"""
36
+
37
+ GetAuthTokens: grpc.UnaryUnaryMultiCallable[
38
+ flwr.proto.exec_pb2.GetAuthTokensRequest,
39
+ flwr.proto.exec_pb2.GetAuthTokensResponse]
40
+ """Get auth tokens upon request"""
41
+
32
42
 
33
43
  class ExecServicer(metaclass=abc.ABCMeta):
34
44
  @abc.abstractmethod
@@ -63,5 +73,21 @@ class ExecServicer(metaclass=abc.ABCMeta):
63
73
  """flwr ls command"""
64
74
  pass
65
75
 
76
+ @abc.abstractmethod
77
+ def GetLoginDetails(self,
78
+ request: flwr.proto.exec_pb2.GetLoginDetailsRequest,
79
+ context: grpc.ServicerContext,
80
+ ) -> flwr.proto.exec_pb2.GetLoginDetailsResponse:
81
+ """Get login details upon request"""
82
+ pass
83
+
84
+ @abc.abstractmethod
85
+ def GetAuthTokens(self,
86
+ request: flwr.proto.exec_pb2.GetAuthTokensRequest,
87
+ context: grpc.ServicerContext,
88
+ ) -> flwr.proto.exec_pb2.GetAuthTokensResponse:
89
+ """Get auth tokens upon request"""
90
+ pass
91
+
66
92
 
67
93
  def add_ExecServicer_to_server(servicer: ExecServicer, server: grpc.Server) -> None: ...
@@ -18,7 +18,7 @@ from flwr.proto import run_pb2 as flwr_dot_proto_dot_run__pb2
18
18
  from flwr.proto import fab_pb2 as flwr_dot_proto_dot_fab__pb2
19
19
 
20
20
 
21
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1d\x66lwr/proto/simulationio.proto\x12\nflwr.proto\x1a\x14\x66lwr/proto/log.proto\x1a\x18\x66lwr/proto/message.proto\x1a\x14\x66lwr/proto/run.proto\x1a\x14\x66lwr/proto/fab.proto\"\x1d\n\x1bPullSimulationInputsRequest\"\x80\x01\n\x1cPullSimulationInputsResponse\x12$\n\x07\x63ontext\x18\x01 \x01(\x0b\x32\x13.flwr.proto.Context\x12\x1c\n\x03run\x18\x02 \x01(\x0b\x32\x0f.flwr.proto.Run\x12\x1c\n\x03\x66\x61\x62\x18\x03 \x01(\x0b\x32\x0f.flwr.proto.Fab\"T\n\x1cPushSimulationOutputsRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\x12$\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x13.flwr.proto.Context\"\x1f\n\x1dPushSimulationOutputsResponse2\xff\x03\n\x0cSimulationIo\x12k\n\x14PullSimulationInputs\x12\'.flwr.proto.PullSimulationInputsRequest\x1a(.flwr.proto.PullSimulationInputsResponse\"\x00\x12n\n\x15PushSimulationOutputs\x12(.flwr.proto.PushSimulationOutputsRequest\x1a).flwr.proto.PushSimulationOutputsResponse\"\x00\x12\\\n\x0fUpdateRunStatus\x12\".flwr.proto.UpdateRunStatusRequest\x1a#.flwr.proto.UpdateRunStatusResponse\"\x00\x12G\n\x08PushLogs\x12\x1b.flwr.proto.PushLogsRequest\x1a\x1c.flwr.proto.PushLogsResponse\"\x00\x12k\n\x14GetFederationOptions\x12\'.flwr.proto.GetFederationOptionsRequest\x1a(.flwr.proto.GetFederationOptionsResponse\"\x00\x62\x06proto3')
21
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1d\x66lwr/proto/simulationio.proto\x12\nflwr.proto\x1a\x14\x66lwr/proto/log.proto\x1a\x18\x66lwr/proto/message.proto\x1a\x14\x66lwr/proto/run.proto\x1a\x14\x66lwr/proto/fab.proto\"\x1d\n\x1bPullSimulationInputsRequest\"\x80\x01\n\x1cPullSimulationInputsResponse\x12$\n\x07\x63ontext\x18\x01 \x01(\x0b\x32\x13.flwr.proto.Context\x12\x1c\n\x03run\x18\x02 \x01(\x0b\x32\x0f.flwr.proto.Run\x12\x1c\n\x03\x66\x61\x62\x18\x03 \x01(\x0b\x32\x0f.flwr.proto.Fab\"T\n\x1cPushSimulationOutputsRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\x12$\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x13.flwr.proto.Context\"\x1f\n\x1dPushSimulationOutputsResponse2\xd4\x04\n\x0cSimulationIo\x12k\n\x14PullSimulationInputs\x12\'.flwr.proto.PullSimulationInputsRequest\x1a(.flwr.proto.PullSimulationInputsResponse\"\x00\x12n\n\x15PushSimulationOutputs\x12(.flwr.proto.PushSimulationOutputsRequest\x1a).flwr.proto.PushSimulationOutputsResponse\"\x00\x12\\\n\x0fUpdateRunStatus\x12\".flwr.proto.UpdateRunStatusRequest\x1a#.flwr.proto.UpdateRunStatusResponse\"\x00\x12G\n\x08PushLogs\x12\x1b.flwr.proto.PushLogsRequest\x1a\x1c.flwr.proto.PushLogsResponse\"\x00\x12k\n\x14GetFederationOptions\x12\'.flwr.proto.GetFederationOptionsRequest\x1a(.flwr.proto.GetFederationOptionsResponse\"\x00\x12S\n\x0cGetRunStatus\x12\x1f.flwr.proto.GetRunStatusRequest\x1a .flwr.proto.GetRunStatusResponse\"\x00\x62\x06proto3')
22
22
 
23
23
  _globals = globals()
24
24
  _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -34,5 +34,5 @@ if _descriptor._USE_C_DESCRIPTORS == False:
34
34
  _globals['_PUSHSIMULATIONOUTPUTSRESPONSE']._serialized_start=385
35
35
  _globals['_PUSHSIMULATIONOUTPUTSRESPONSE']._serialized_end=416
36
36
  _globals['_SIMULATIONIO']._serialized_start=419
37
- _globals['_SIMULATIONIO']._serialized_end=930
37
+ _globals['_SIMULATIONIO']._serialized_end=1015
38
38
  # @@protoc_insertion_point(module_scope)
@@ -41,6 +41,11 @@ class SimulationIoStub(object):
41
41
  request_serializer=flwr_dot_proto_dot_run__pb2.GetFederationOptionsRequest.SerializeToString,
42
42
  response_deserializer=flwr_dot_proto_dot_run__pb2.GetFederationOptionsResponse.FromString,
43
43
  )
44
+ self.GetRunStatus = channel.unary_unary(
45
+ '/flwr.proto.SimulationIo/GetRunStatus',
46
+ request_serializer=flwr_dot_proto_dot_run__pb2.GetRunStatusRequest.SerializeToString,
47
+ response_deserializer=flwr_dot_proto_dot_run__pb2.GetRunStatusResponse.FromString,
48
+ )
44
49
 
45
50
 
46
51
  class SimulationIoServicer(object):
@@ -81,6 +86,13 @@ class SimulationIoServicer(object):
81
86
  context.set_details('Method not implemented!')
82
87
  raise NotImplementedError('Method not implemented!')
83
88
 
89
+ def GetRunStatus(self, request, context):
90
+ """Get Run Status
91
+ """
92
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
93
+ context.set_details('Method not implemented!')
94
+ raise NotImplementedError('Method not implemented!')
95
+
84
96
 
85
97
  def add_SimulationIoServicer_to_server(servicer, server):
86
98
  rpc_method_handlers = {
@@ -109,6 +121,11 @@ def add_SimulationIoServicer_to_server(servicer, server):
109
121
  request_deserializer=flwr_dot_proto_dot_run__pb2.GetFederationOptionsRequest.FromString,
110
122
  response_serializer=flwr_dot_proto_dot_run__pb2.GetFederationOptionsResponse.SerializeToString,
111
123
  ),
124
+ 'GetRunStatus': grpc.unary_unary_rpc_method_handler(
125
+ servicer.GetRunStatus,
126
+ request_deserializer=flwr_dot_proto_dot_run__pb2.GetRunStatusRequest.FromString,
127
+ response_serializer=flwr_dot_proto_dot_run__pb2.GetRunStatusResponse.SerializeToString,
128
+ ),
112
129
  }
113
130
  generic_handler = grpc.method_handlers_generic_handler(
114
131
  'flwr.proto.SimulationIo', rpc_method_handlers)
@@ -203,3 +220,20 @@ class SimulationIo(object):
203
220
  flwr_dot_proto_dot_run__pb2.GetFederationOptionsResponse.FromString,
204
221
  options, channel_credentials,
205
222
  insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
223
+
224
+ @staticmethod
225
+ def GetRunStatus(request,
226
+ target,
227
+ options=(),
228
+ channel_credentials=None,
229
+ call_credentials=None,
230
+ insecure=False,
231
+ compression=None,
232
+ wait_for_ready=None,
233
+ timeout=None,
234
+ metadata=None):
235
+ return grpc.experimental.unary_unary(request, target, '/flwr.proto.SimulationIo/GetRunStatus',
236
+ flwr_dot_proto_dot_run__pb2.GetRunStatusRequest.SerializeToString,
237
+ flwr_dot_proto_dot_run__pb2.GetRunStatusResponse.FromString,
238
+ options, channel_credentials,
239
+ insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
@@ -35,6 +35,11 @@ class SimulationIoStub:
35
35
  flwr.proto.run_pb2.GetFederationOptionsResponse]
36
36
  """Get Federation Options"""
37
37
 
38
+ GetRunStatus: grpc.UnaryUnaryMultiCallable[
39
+ flwr.proto.run_pb2.GetRunStatusRequest,
40
+ flwr.proto.run_pb2.GetRunStatusResponse]
41
+ """Get Run Status"""
42
+
38
43
 
39
44
  class SimulationIoServicer(metaclass=abc.ABCMeta):
40
45
  @abc.abstractmethod
@@ -77,5 +82,13 @@ class SimulationIoServicer(metaclass=abc.ABCMeta):
77
82
  """Get Federation Options"""
78
83
  pass
79
84
 
85
+ @abc.abstractmethod
86
+ def GetRunStatus(self,
87
+ request: flwr.proto.run_pb2.GetRunStatusRequest,
88
+ context: grpc.ServicerContext,
89
+ ) -> flwr.proto.run_pb2.GetRunStatusResponse:
90
+ """Get Run Status"""
91
+ pass
92
+
80
93
 
81
94
  def add_SimulationIoServicer_to_server(servicer: SimulationIoServicer, server: grpc.Server) -> None: ...
flwr/server/app.py CHANGED
@@ -24,9 +24,10 @@ from collections.abc import Sequence
24
24
  from logging import DEBUG, INFO, WARN
25
25
  from pathlib import Path
26
26
  from time import sleep
27
- from typing import Optional
27
+ from typing import Any, Optional
28
28
 
29
29
  import grpc
30
+ import yaml
30
31
  from cryptography.exceptions import UnsupportedAlgorithm
31
32
  from cryptography.hazmat.primitives.asymmetric import ec
32
33
  from cryptography.hazmat.primitives.serialization import (
@@ -37,8 +38,10 @@ from cryptography.hazmat.primitives.serialization import (
37
38
  from flwr.common import GRPC_MAX_MESSAGE_LENGTH, EventType, event
38
39
  from flwr.common.address import parse_address
39
40
  from flwr.common.args import try_obtain_server_certificates
41
+ from flwr.common.auth_plugin import ExecAuthPlugin
40
42
  from flwr.common.config import get_flwr_dir, parse_config_args
41
43
  from flwr.common.constant import (
44
+ AUTH_TYPE,
42
45
  CLIENT_OCTET,
43
46
  EXEC_API_DEFAULT_SERVER_ADDRESS,
44
47
  FLEET_API_GRPC_BIDI_DEFAULT_ADDRESS,
@@ -88,6 +91,15 @@ DATABASE = ":flwr-in-memory-state:"
88
91
  BASE_DIR = get_flwr_dir() / "superlink" / "ffs"
89
92
 
90
93
 
94
+ try:
95
+ from flwr.ee import get_exec_auth_plugins
96
+ except ImportError:
97
+
98
+ def get_exec_auth_plugins() -> dict[str, type[ExecAuthPlugin]]:
99
+ """Return all Exec API authentication plugins."""
100
+ raise NotImplementedError("No authentication plugins are currently supported.")
101
+
102
+
91
103
  def start_server( # pylint: disable=too-many-arguments,too-many-locals
92
104
  *,
93
105
  server_address: str = FLEET_API_GRPC_BIDI_DEFAULT_ADDRESS,
@@ -246,6 +258,12 @@ def run_superlink() -> None:
246
258
  # Obtain certificates
247
259
  certificates = try_obtain_server_certificates(args, args.fleet_api_type)
248
260
 
261
+ user_auth_config = _try_obtain_user_auth_config(args)
262
+ auth_plugin: Optional[ExecAuthPlugin] = None
263
+ # user_auth_config is None only if the args.user_auth_config is not provided
264
+ if user_auth_config is not None:
265
+ auth_plugin = _try_obtain_exec_auth_plugin(user_auth_config)
266
+
249
267
  # Initialize StateFactory
250
268
  state_factory = LinkStateFactory(args.database)
251
269
 
@@ -263,6 +281,7 @@ def run_superlink() -> None:
263
281
  config=parse_config_args(
264
282
  [args.executor_config] if args.executor_config else args.executor_config
265
283
  ),
284
+ auth_plugin=auth_plugin,
266
285
  )
267
286
  grpc_servers = [exec_server]
268
287
 
@@ -559,6 +578,32 @@ def _try_setup_node_authentication(
559
578
  )
560
579
 
561
580
 
581
+ def _try_obtain_user_auth_config(args: argparse.Namespace) -> Optional[dict[str, Any]]:
582
+ if args.user_auth_config is not None:
583
+ with open(args.user_auth_config, encoding="utf-8") as file:
584
+ config: dict[str, Any] = yaml.safe_load(file)
585
+ return config
586
+ return None
587
+
588
+
589
+ def _try_obtain_exec_auth_plugin(config: dict[str, Any]) -> Optional[ExecAuthPlugin]:
590
+ auth_config: dict[str, Any] = config.get("authentication", {})
591
+ auth_type: str = auth_config.get(AUTH_TYPE, "")
592
+ try:
593
+ all_plugins: dict[str, type[ExecAuthPlugin]] = get_exec_auth_plugins()
594
+ auth_plugin_class = all_plugins[auth_type]
595
+ return auth_plugin_class(config=auth_config)
596
+ except KeyError:
597
+ if auth_type != "":
598
+ sys.exit(
599
+ f'Authentication type "{auth_type}" is not supported. '
600
+ "Please provide a valid authentication type in the configuration."
601
+ )
602
+ sys.exit("No authentication type is provided in the configuration.")
603
+ except NotImplementedError:
604
+ sys.exit("No authentication plugins are currently supported.")
605
+
606
+
562
607
  def _run_fleet_api_grpc_rere(
563
608
  address: str,
564
609
  state_factory: LinkStateFactory,
@@ -746,6 +791,12 @@ def _add_args_common(parser: argparse.ArgumentParser) -> None:
746
791
  type=str,
747
792
  help="The SuperLink's public key (as a path str) to enable authentication.",
748
793
  )
794
+ parser.add_argument(
795
+ "--user-auth-config",
796
+ help="The path to the user authentication configuration YAML file.",
797
+ type=str,
798
+ default=None,
799
+ )
749
800
 
750
801
 
751
802
  def _add_args_serverappio_api(parser: argparse.ArgumentParser) -> None:
@@ -28,6 +28,7 @@ from flwr.common.serde import (
28
28
  context_to_proto,
29
29
  fab_to_proto,
30
30
  run_status_from_proto,
31
+ run_status_to_proto,
31
32
  run_to_proto,
32
33
  )
33
34
  from flwr.common.typing import Fab, RunStatus
@@ -39,6 +40,8 @@ from flwr.proto.log_pb2 import ( # pylint: disable=E0611
39
40
  from flwr.proto.run_pb2 import ( # pylint: disable=E0611
40
41
  GetFederationOptionsRequest,
41
42
  GetFederationOptionsResponse,
43
+ GetRunStatusRequest,
44
+ GetRunStatusResponse,
42
45
  UpdateRunStatusRequest,
43
46
  UpdateRunStatusResponse,
44
47
  )
@@ -122,6 +125,22 @@ class SimulationIoServicer(simulationio_pb2_grpc.SimulationIoServicer):
122
125
  )
123
126
  return UpdateRunStatusResponse()
124
127
 
128
+ def GetRunStatus(
129
+ self, request: GetRunStatusRequest, context: ServicerContext
130
+ ) -> GetRunStatusResponse:
131
+ """Get status of requested runs."""
132
+ log(DEBUG, "SimultionIoServicer.GetRunStatus")
133
+ state = self.state_factory.state()
134
+
135
+ statuses = state.get_run_status(set(request.run_ids))
136
+
137
+ return GetRunStatusResponse(
138
+ run_status_dict={
139
+ run_id: run_status_to_proto(status)
140
+ for run_id, status in statuses.items()
141
+ }
142
+ )
143
+
125
144
  def PushLogs(
126
145
  self, request: PushLogsRequest, context: grpc.ServicerContext
127
146
  ) -> PushLogsResponse:
@@ -14,18 +14,21 @@
14
14
  # ==============================================================================
15
15
  """SuperExec gRPC API."""
16
16
 
17
+ from collections.abc import Sequence
17
18
  from logging import INFO
18
19
  from typing import Optional
19
20
 
20
21
  import grpc
21
22
 
22
23
  from flwr.common import GRPC_MAX_MESSAGE_LENGTH
24
+ from flwr.common.auth_plugin import ExecAuthPlugin
23
25
  from flwr.common.logger import log
24
26
  from flwr.common.typing import UserConfig
25
27
  from flwr.proto.exec_pb2_grpc import add_ExecServicer_to_server
26
28
  from flwr.server.superlink.ffs.ffs_factory import FfsFactory
27
29
  from flwr.server.superlink.fleet.grpc_bidi.grpc_server import generic_create_grpc_server
28
30
  from flwr.server.superlink.linkstate import LinkStateFactory
31
+ from flwr.superexec.exec_user_auth_interceptor import ExecUserAuthInterceptor
29
32
 
30
33
  from .exec_servicer import ExecServicer
31
34
  from .executor import Executor
@@ -39,6 +42,7 @@ def run_exec_api_grpc(
39
42
  ffs_factory: FfsFactory,
40
43
  certificates: Optional[tuple[bytes, bytes, bytes]],
41
44
  config: UserConfig,
45
+ auth_plugin: Optional[ExecAuthPlugin] = None,
42
46
  ) -> grpc.Server:
43
47
  """Run Exec API (gRPC, request-response)."""
44
48
  executor.set_config(config)
@@ -47,16 +51,29 @@ def run_exec_api_grpc(
47
51
  linkstate_factory=state_factory,
48
52
  ffs_factory=ffs_factory,
49
53
  executor=executor,
54
+ auth_plugin=auth_plugin,
50
55
  )
56
+ interceptors: Optional[Sequence[grpc.ServerInterceptor]] = None
57
+ if auth_plugin is not None:
58
+ interceptors = [ExecUserAuthInterceptor(auth_plugin)]
51
59
  exec_add_servicer_to_server_fn = add_ExecServicer_to_server
52
60
  exec_grpc_server = generic_create_grpc_server(
53
61
  servicer_and_add_fn=(exec_servicer, exec_add_servicer_to_server_fn),
54
62
  server_address=address,
55
63
  max_message_length=GRPC_MAX_MESSAGE_LENGTH,
56
64
  certificates=certificates,
65
+ interceptors=interceptors,
57
66
  )
58
67
 
59
- log(INFO, "Flower Deployment Engine: Starting Exec API on %s", address)
68
+ if auth_plugin is None:
69
+ log(INFO, "Flower Deployment Engine: Starting Exec API on %s", address)
70
+ else:
71
+ log(
72
+ INFO,
73
+ "Flower Deployment Engine: Starting Exec API with user "
74
+ "authentication on %s",
75
+ address,
76
+ )
60
77
  exec_grpc_server.start()
61
78
 
62
79
  return exec_grpc_server
@@ -18,11 +18,12 @@
18
18
  import time
19
19
  from collections.abc import Generator
20
20
  from logging import ERROR, INFO
21
- from typing import Any
21
+ from typing import Any, Optional
22
22
 
23
23
  import grpc
24
24
 
25
25
  from flwr.common import now
26
+ from flwr.common.auth_plugin import ExecAuthPlugin
26
27
  from flwr.common.constant import LOG_STREAM_INTERVAL, Status, SubStatus
27
28
  from flwr.common.logger import log
28
29
  from flwr.common.serde import (
@@ -33,6 +34,10 @@ from flwr.common.serde import (
33
34
  from flwr.common.typing import RunStatus
34
35
  from flwr.proto import exec_pb2_grpc # pylint: disable=E0611
35
36
  from flwr.proto.exec_pb2 import ( # pylint: disable=E0611
37
+ GetAuthTokensRequest,
38
+ GetAuthTokensResponse,
39
+ GetLoginDetailsRequest,
40
+ GetLoginDetailsResponse,
36
41
  ListRunsRequest,
37
42
  ListRunsResponse,
38
43
  StartRunRequest,
@@ -56,11 +61,13 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
56
61
  linkstate_factory: LinkStateFactory,
57
62
  ffs_factory: FfsFactory,
58
63
  executor: Executor,
64
+ auth_plugin: Optional[ExecAuthPlugin] = None,
59
65
  ) -> None:
60
66
  self.linkstate_factory = linkstate_factory
61
67
  self.ffs_factory = ffs_factory
62
68
  self.executor = executor
63
69
  self.executor.initialize(linkstate_factory, ffs_factory)
70
+ self.auth_plugin = auth_plugin
64
71
 
65
72
  def StartRun(
66
73
  self, request: StartRunRequest, context: grpc.ServicerContext
@@ -155,6 +162,36 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
155
162
  )
156
163
  return StopRunResponse(success=update_success)
157
164
 
165
+ def GetLoginDetails(
166
+ self, request: GetLoginDetailsRequest, context: grpc.ServicerContext
167
+ ) -> GetLoginDetailsResponse:
168
+ """Start login."""
169
+ log(INFO, "ExecServicer.GetLoginDetails")
170
+ if self.auth_plugin is None:
171
+ context.abort(
172
+ grpc.StatusCode.UNIMPLEMENTED,
173
+ "ExecServicer initialized without user authentication",
174
+ )
175
+ raise grpc.RpcError() # This line is unreachable
176
+ return GetLoginDetailsResponse(
177
+ login_details=self.auth_plugin.get_login_details()
178
+ )
179
+
180
+ def GetAuthTokens(
181
+ self, request: GetAuthTokensRequest, context: grpc.ServicerContext
182
+ ) -> GetAuthTokensResponse:
183
+ """Get auth token."""
184
+ log(INFO, "ExecServicer.GetAuthTokens")
185
+ if self.auth_plugin is None:
186
+ context.abort(
187
+ grpc.StatusCode.UNIMPLEMENTED,
188
+ "ExecServicer initialized without user authentication",
189
+ )
190
+ raise grpc.RpcError() # This line is unreachable
191
+ return GetAuthTokensResponse(
192
+ auth_tokens=self.auth_plugin.get_auth_tokens(dict(request.auth_details))
193
+ )
194
+
158
195
 
159
196
  def _create_list_runs_response(run_ids: set[int], state: LinkState) -> ListRunsResponse:
160
197
  """Create response for `flwr ls --runs` and `flwr ls --run-id <run_id>`."""
@@ -0,0 +1,101 @@
1
+ # Copyright 2024 Flower Labs GmbH. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+ """Flower Exec API interceptor."""
16
+
17
+
18
+ from typing import Any, Callable, Union
19
+
20
+ import grpc
21
+
22
+ from flwr.common.auth_plugin import ExecAuthPlugin
23
+ from flwr.proto.exec_pb2 import ( # pylint: disable=E0611
24
+ GetAuthTokensRequest,
25
+ GetAuthTokensResponse,
26
+ GetLoginDetailsRequest,
27
+ GetLoginDetailsResponse,
28
+ StartRunRequest,
29
+ StartRunResponse,
30
+ StreamLogsRequest,
31
+ StreamLogsResponse,
32
+ )
33
+
34
+ Request = Union[
35
+ StartRunRequest,
36
+ StreamLogsRequest,
37
+ GetLoginDetailsRequest,
38
+ GetAuthTokensRequest,
39
+ ]
40
+
41
+ Response = Union[
42
+ StartRunResponse, StreamLogsResponse, GetLoginDetailsResponse, GetAuthTokensResponse
43
+ ]
44
+
45
+
46
+ class ExecUserAuthInterceptor(grpc.ServerInterceptor): # type: ignore
47
+ """Exec API interceptor for user authentication."""
48
+
49
+ def __init__(
50
+ self,
51
+ auth_plugin: ExecAuthPlugin,
52
+ ):
53
+ self.auth_plugin = auth_plugin
54
+
55
+ def intercept_service(
56
+ self,
57
+ continuation: Callable[[Any], Any],
58
+ handler_call_details: grpc.HandlerCallDetails,
59
+ ) -> grpc.RpcMethodHandler:
60
+ """Flower server interceptor authentication logic.
61
+
62
+ Intercept all unary-unary/unary-stream calls from users and authenticate users
63
+ by validating auth metadata sent by the user. Continue RPC call if user is
64
+ authenticated, else, terminate RPC call by setting context to abort.
65
+ """
66
+ # One of the method handlers in
67
+ # `flwr.superexec.exec_servicer.ExecServicer`
68
+ method_handler: grpc.RpcMethodHandler = continuation(handler_call_details)
69
+ return self._generic_auth_unary_method_handler(method_handler)
70
+
71
+ def _generic_auth_unary_method_handler(
72
+ self, method_handler: grpc.RpcMethodHandler
73
+ ) -> grpc.RpcMethodHandler:
74
+ def _generic_method_handler(
75
+ request: Request,
76
+ context: grpc.ServicerContext,
77
+ ) -> Response:
78
+ call = method_handler.unary_unary or method_handler.unary_stream
79
+ metadata = context.invocation_metadata()
80
+ if isinstance(
81
+ request, (GetLoginDetailsRequest, GetAuthTokensRequest)
82
+ ) or self.auth_plugin.validate_tokens_in_metadata(metadata):
83
+ return call(request, context) # type: ignore
84
+
85
+ tokens = self.auth_plugin.refresh_tokens(context.invocation_metadata())
86
+ if tokens is not None:
87
+ context.send_initial_metadata(tokens)
88
+ return call(request, context) # type: ignore
89
+
90
+ context.abort(grpc.StatusCode.UNAUTHENTICATED, "Access denied")
91
+ raise grpc.RpcError() # This line is unreachable
92
+
93
+ if method_handler.unary_unary:
94
+ message_handler = grpc.unary_unary_rpc_method_handler
95
+ else:
96
+ message_handler = grpc.unary_stream_rpc_method_handler
97
+ return message_handler(
98
+ _generic_method_handler,
99
+ request_deserializer=method_handler.request_deserializer,
100
+ response_serializer=method_handler.response_serializer,
101
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: flwr-nightly
3
- Version: 1.14.0.dev20241208
3
+ Version: 1.14.0.dev20241210
4
4
  Summary: Flower: A Friendly Federated AI Framework
5
5
  Home-page: https://flower.ai
6
6
  License: Apache-2.0
@@ -39,8 +39,9 @@ Requires-Dist: numpy (>=1.26.0,<3.0.0)
39
39
  Requires-Dist: pathspec (>=0.12.1,<0.13.0)
40
40
  Requires-Dist: protobuf (>=4.25.2,<5.0.0)
41
41
  Requires-Dist: pycryptodome (>=3.18.0,<4.0.0)
42
+ Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
42
43
  Requires-Dist: ray (==2.10.0) ; (python_version >= "3.9" and python_version < "3.12") and (extra == "simulation")
43
- Requires-Dist: requests (>=2.31.0,<3.0.0) ; extra == "rest"
44
+ Requires-Dist: requests (>=2.31.0,<3.0.0)
44
45
  Requires-Dist: rich (>=13.5.0,<14.0.0)
45
46
  Requires-Dist: starlette (>=0.31.0,<0.32.0) ; extra == "rest"
46
47
  Requires-Dist: tomli (>=2.0.1,<3.0.0)
@@ -108,8 +108,10 @@ flwr/client/typing.py,sha256=dxoTBnTMfqXr5J7G3y-uNjqxYCddvxhu89spfj4Lm2U,1048
108
108
  flwr/common/__init__.py,sha256=TVaoFEJE158aui1TPZQiJCDZX4RNHRyI8I55VC80HhI,3901
109
109
  flwr/common/address.py,sha256=7kM2Rqjw86-c8aKwAvrXerWqznnVv4TFJ62aSAeTn10,3017
110
110
  flwr/common/args.py,sha256=-KeQ6AZw1-G4Ifhsg4qlRnWhGH1m_OzUgxH7Z4j_0ns,6222
111
+ flwr/common/auth_plugin/__init__.py,sha256=1Y8Oj3iB49IHDu9tvDih1J74Ygu7k85V9s2A4WORPyA,887
112
+ flwr/common/auth_plugin/auth_plugin.py,sha256=6WEAVVPrS7LgSBpd4WyHYU4EsajT2nBGI_IN3mhYzoU,3567
111
113
  flwr/common/config.py,sha256=qC1QvGAGr4faBtg3Y5dWhfyK5FggyWUMjPqg-Rx_FW4,8083
112
- flwr/common/constant.py,sha256=G1arzDznYIlhUpkrk31-k-pJsRcOuoAoscI6bGe59nE,5792
114
+ flwr/common/constant.py,sha256=-HoTq6u_9VGbva21qTm_vfvQV9cxV7LwsvvlHEBjNwk,5817
113
115
  flwr/common/context.py,sha256=uJ-mnoC_8y_udEb3kAX-r8CPphNTWM72z1AlsvQEu54,2403
114
116
  flwr/common/date.py,sha256=NHHpESce5wYqEwoDXf09gp9U9l_5Bmlh2BsOcwS-kDM,1554
115
117
  flwr/common/differential_privacy.py,sha256=XwcJ3rWr8S8BZUocc76vLSJAXIf6OHnWkBV6-xlIRuw,6106
@@ -152,10 +154,10 @@ flwr/proto/error_pb2.py,sha256=LarjKL90LbwkXKlhzNrDssgl4DXcvIPve8NVCXHpsKA,1084
152
154
  flwr/proto/error_pb2.pyi,sha256=ZNH4HhJTU_KfMXlyCeg8FwU-fcUYxTqEmoJPtWtHikc,734
153
155
  flwr/proto/error_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
154
156
  flwr/proto/error_pb2_grpc.pyi,sha256=ff2TSiLVnG6IVQcTGzb2DIH3XRSoAvAo_RMcvbMFyc0,76
155
- flwr/proto/exec_pb2.py,sha256=dSxDJm2rRXw7zhV6MTrJJyU51oqMNWDm0JUzVvD86BI,4484
156
- flwr/proto/exec_pb2.pyi,sha256=xWfRQHFOLAxPqLkAaecihNK5_zlAfqE0NUJuSjPViXw,7145
157
- flwr/proto/exec_pb2_grpc.py,sha256=z_9Hw-VapmkyfNlAOyvy2xJel1jo6P2oZgduOxnHPeE,7163
158
- flwr/proto/exec_pb2_grpc.pyi,sha256=VDsnz9vJeweJdKCNyygDqSDfyR6LoHA4mwWViF2f7AI,2000
157
+ flwr/proto/exec_pb2.py,sha256=IVqmpzzThSjuLBCF8T9VofTpnUXtp3SYWOEp8dzyv5o,6883
158
+ flwr/proto/exec_pb2.pyi,sha256=amt-3e3zJVjkRlQ8Gz6m1A7hXyeZmbQhHpAEIQyIDn0,10660
159
+ flwr/proto/exec_pb2_grpc.py,sha256=-bdLqjsqQxK9R8LIiZaKlLKH2NmjR50EaGKTPPTwFhI,10445
160
+ flwr/proto/exec_pb2_grpc.pyi,sha256=M5k-FzeLWxal7zt28LJfzMWWRxmNknTC2BzHRRMa1sQ,2914
159
161
  flwr/proto/fab_pb2.py,sha256=3QSDq9pjbZoqVxsmCRDwHO5PrSjzn2vixjYxE-qPmb0,1589
160
162
  flwr/proto/fab_pb2.pyi,sha256=fXI108QaFtbl1WWTyslPbIx9c_19D0aYCoFn0xYtL4U,2277
161
163
  flwr/proto/fab_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
@@ -192,10 +194,10 @@ flwr/proto/serverappio_pb2.py,sha256=zWnODeaj26oSx98-BFvwtWM_fYvsw9OeSIuV7JnKVvw
192
194
  flwr/proto/serverappio_pb2.pyi,sha256=Ib9c32FCtjA9zZY54Ohi6B-DtLgSjokAqOExm_2uOvY,6429
193
195
  flwr/proto/serverappio_pb2_grpc.py,sha256=M__pFMmb9yTAGMHVd3_K1V6DeLRuFc9UErJHWjBAsZs,17439
194
196
  flwr/proto/serverappio_pb2_grpc.pyi,sha256=ERM-0cQVmUqrVXlvEbS2wfUZpZmv5SlIeNsGZPYMrVo,4779
195
- flwr/proto/simulationio_pb2.py,sha256=sCJQp_NEJSDtC4EKzyy2yZWtu9z7PGUUFJpLjdA9VUs,3011
197
+ flwr/proto/simulationio_pb2.py,sha256=Fv7m8d4vR_0CIGU93nN5tDXSCk2QPbASH_8mT2wBPTE,3117
196
198
  flwr/proto/simulationio_pb2.pyi,sha256=oXx8_FLBe5B54wduZj-f89kub73XxNtQbThuW8YfPAs,2660
197
- flwr/proto/simulationio_pb2_grpc.py,sha256=-qcd4rOOK-46LcP57oZswqwASpy2_UvMJmJ80OpzuNM,9622
198
- flwr/proto/simulationio_pb2_grpc.pyi,sha256=Bk4K7MsPpxF6RmIqByySQIXJeJ1pBrU2I19hrKUoFdI,2795
199
+ flwr/proto/simulationio_pb2_grpc.py,sha256=9I3yAfJaeMuG-qH_5Ge45eFOftsIOmL9b8E_xHmcvKw,11232
200
+ flwr/proto/simulationio_pb2_grpc.pyi,sha256=YHvKtyo7UdbBgdhoN0ndzZeB5vIC3JuR5PAJLrl-OKM,3206
199
201
  flwr/proto/task_pb2.py,sha256=R5GfHgL8IJRI_qHWNeILl1Y9zHjvB0tnCvMHmTgF4Is,2361
200
202
  flwr/proto/task_pb2.pyi,sha256=KJVsLm-THY5QjHreHDm_-OS1tyZyD61mx6BzOpoeMjw,4320
201
203
  flwr/proto/task_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
@@ -206,7 +208,7 @@ flwr/proto/transport_pb2_grpc.py,sha256=vLN3EHtx2aEEMCO4f1Upu-l27BPzd3-5pV-u8wPc
206
208
  flwr/proto/transport_pb2_grpc.pyi,sha256=AGXf8RiIiW2J5IKMlm_3qT3AzcDa4F3P5IqUjve_esA,766
207
209
  flwr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
208
210
  flwr/server/__init__.py,sha256=cEg1oecBu4cKB69iJCqWEylC8b5XW47bl7rQiJsdTvM,1528
209
- flwr/server/app.py,sha256=wes1HI5KJfzPry7bjztQDazn1FIca3CS6kEVM_6LLUY,28872
211
+ flwr/server/app.py,sha256=c9XHwSYuTL3xU9BKEbKeEJ0frWveectLjSr_EA4lnT8,30868
210
212
  flwr/server/client_manager.py,sha256=7Ese0tgrH-i-ms363feYZJKwB8gWnXSmg_hYF2Bju4U,6227
211
213
  flwr/server/client_proxy.py,sha256=4G-oTwhb45sfWLx2uZdcXD98IZwdTS6F88xe3akCdUg,2399
212
214
  flwr/server/compat/__init__.py,sha256=VxnJtJyOjNFQXMNi9hIuzNlZM5n0Hj1p3aq_Pm2udw4,892
@@ -287,7 +289,7 @@ flwr/server/superlink/linkstate/sqlite_linkstate.py,sha256=XWr-2CWZTWCrGdXV_wYBB
287
289
  flwr/server/superlink/linkstate/utils.py,sha256=d5uqqIOCKfd54X8CFNfUr3AWqPLpgmzsC_RagRwFugM,13321
288
290
  flwr/server/superlink/simulation/__init__.py,sha256=mg-oapC9dkzEfjXPQFior5lpWj4g9kwbLovptyYM_g0,718
289
291
  flwr/server/superlink/simulation/simulationio_grpc.py,sha256=5wflYW_TS0mjmPG6OYuHMJwXD2_cYmUNhFkdOU0jMWQ,2237
290
- flwr/server/superlink/simulation/simulationio_servicer.py,sha256=LsW6Cl8qH_vq04F6CeOp3vBtjTGQn4tATKHfirDmJZQ,5942
292
+ flwr/server/superlink/simulation/simulationio_servicer.py,sha256=riaZm090aTs7o8cFD8gvCWkX7A2SPLXKM4K8MG60av8,6545
291
293
  flwr/server/typing.py,sha256=5kaRLZuxTEse9A0g7aVna2VhYxU3wTq1f3d3mtw7kXs,1019
292
294
  flwr/server/utils/__init__.py,sha256=pltsPHJoXmUIr3utjwwYxu7_ZAGy5u4MVHzv9iA5Un8,908
293
295
  flwr/server/utils/tensorboard.py,sha256=gEBD8w_5uaIfp5aw5RYH66lYZpd_SfkObHQ7eDd9MUk,5466
@@ -310,12 +312,13 @@ flwr/simulation/simulationio_connection.py,sha256=m31L9Iej-61va48E5x-wJypA6p5s82
310
312
  flwr/superexec/__init__.py,sha256=fcj366jh4RFby_vDwLroU4kepzqbnJgseZD_jUr_Mko,715
311
313
  flwr/superexec/app.py,sha256=Tt3GonnTwHrMmicwx9XaP-crP78-bf4DUWl-N5cG6zY,1841
312
314
  flwr/superexec/deployment.py,sha256=7VYmmI12zEaTHp_cQtU1GLikmqhctUHhEdshBPRFHMs,6734
313
- flwr/superexec/exec_grpc.py,sha256=OuhBAk7hiky9rjGceinLGIXqchtzGPQThZnwyYv6Ei0,2241
314
- flwr/superexec/exec_servicer.py,sha256=qHWbGRuP702-JxlxFuztGlRdRoNET8G-0m1xwnoZgig,6016
315
+ flwr/superexec/exec_grpc.py,sha256=hG1bxAbwB7Wt7R73931ib_UIcWvY628IIqk5rk3b25o,2896
316
+ flwr/superexec/exec_servicer.py,sha256=jEYcASzkQR1ftjzilmlcTPKXo8NSno9mSj_UbBvMjGM,7467
317
+ flwr/superexec/exec_user_auth_interceptor.py,sha256=K06OU-l4LnYhTDg071hGJuOaQWEJbZsYi5qxUmmtiG0,3704
315
318
  flwr/superexec/executor.py,sha256=zH3_53il6Jh0ZscIVEB9f4GNnXMeBbCGyCoBCxLgiG0,3114
316
319
  flwr/superexec/simulation.py,sha256=WQDon15oqpMopAZnwRZoTICYCfHqtkvFSqiTQ2hLD_g,4088
317
- flwr_nightly-1.14.0.dev20241208.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
318
- flwr_nightly-1.14.0.dev20241208.dist-info/METADATA,sha256=gRPLRI4oVh__pq8EKcawfWKgN8zkz7nB-4QnrtFiWfs,15679
319
- flwr_nightly-1.14.0.dev20241208.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
320
- flwr_nightly-1.14.0.dev20241208.dist-info/entry_points.txt,sha256=JlNxX3qhaV18_2yj5a3kJW1ESxm31cal9iS_N_pf1Rk,538
321
- flwr_nightly-1.14.0.dev20241208.dist-info/RECORD,,
320
+ flwr_nightly-1.14.0.dev20241210.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
321
+ flwr_nightly-1.14.0.dev20241210.dist-info/METADATA,sha256=3DjOUcyJbLgmX8glq_Tt7SV70giiQh5JP39zfpFyLjE,15700
322
+ flwr_nightly-1.14.0.dev20241210.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
323
+ flwr_nightly-1.14.0.dev20241210.dist-info/entry_points.txt,sha256=JlNxX3qhaV18_2yj5a3kJW1ESxm31cal9iS_N_pf1Rk,538
324
+ flwr_nightly-1.14.0.dev20241210.dist-info/RECORD,,