flwr 1.22.0__py3-none-any.whl → 1.24.0__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.
Files changed (301) hide show
  1. flwr/__init__.py +16 -5
  2. flwr/app/error.py +2 -2
  3. flwr/app/exception.py +3 -3
  4. flwr/cli/app.py +34 -1
  5. flwr/cli/app_cmd/__init__.py +23 -0
  6. flwr/cli/app_cmd/publish.py +285 -0
  7. flwr/cli/app_cmd/review.py +252 -0
  8. flwr/cli/auth_plugin/__init__.py +15 -6
  9. flwr/cli/auth_plugin/auth_plugin.py +94 -0
  10. flwr/cli/auth_plugin/noop_auth_plugin.py +101 -0
  11. flwr/cli/auth_plugin/oidc_cli_plugin.py +46 -32
  12. flwr/cli/build.py +166 -53
  13. flwr/cli/{cli_user_auth_interceptor.py → cli_account_auth_interceptor.py} +29 -11
  14. flwr/cli/config_utils.py +101 -13
  15. flwr/cli/federation/__init__.py +24 -0
  16. flwr/cli/federation/ls.py +140 -0
  17. flwr/cli/federation/show.py +317 -0
  18. flwr/cli/install.py +91 -13
  19. flwr/cli/log.py +54 -11
  20. flwr/cli/login/login.py +41 -27
  21. flwr/cli/ls.py +177 -133
  22. flwr/cli/new/new.py +175 -40
  23. flwr/cli/new/templates/app/code/task.pytorch.py.tpl +1 -0
  24. flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +1 -1
  25. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +1 -1
  26. flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +1 -1
  27. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
  28. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -1
  29. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
  30. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +3 -3
  31. flwr/cli/new/templates/app/pyproject.pytorch_legacy_api.toml.tpl +1 -1
  32. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
  33. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +2 -2
  34. flwr/cli/new/templates/app/pyproject.xgboost.toml.tpl +1 -1
  35. flwr/cli/pull.py +12 -7
  36. flwr/cli/run/run.py +82 -31
  37. flwr/cli/run_utils.py +130 -0
  38. flwr/cli/stop.py +27 -9
  39. flwr/cli/supernode/__init__.py +25 -0
  40. flwr/cli/supernode/ls.py +268 -0
  41. flwr/cli/supernode/register.py +190 -0
  42. flwr/cli/supernode/unregister.py +140 -0
  43. flwr/cli/utils.py +464 -81
  44. flwr/client/__init__.py +2 -1
  45. flwr/client/dpfedavg_numpy_client.py +4 -1
  46. flwr/client/grpc_adapter_client/connection.py +12 -15
  47. flwr/client/grpc_rere_client/connection.py +68 -41
  48. flwr/client/grpc_rere_client/grpc_adapter.py +34 -14
  49. flwr/client/grpc_rere_client/{client_interceptor.py → node_auth_client_interceptor.py} +5 -7
  50. flwr/client/message_handler/message_handler.py +2 -2
  51. flwr/client/mod/secure_aggregation/secaggplus_mod.py +10 -8
  52. flwr/client/numpy_client.py +1 -1
  53. flwr/client/rest_client/connection.py +94 -51
  54. flwr/client/run_info_store.py +4 -5
  55. flwr/client/typing.py +1 -1
  56. flwr/clientapp/__init__.py +1 -2
  57. flwr/{client → clientapp}/client_app.py +9 -10
  58. flwr/clientapp/mod/centraldp_mods.py +16 -17
  59. flwr/clientapp/mod/localdp_mod.py +8 -9
  60. flwr/clientapp/typing.py +1 -1
  61. flwr/{client/clientapp → clientapp}/utils.py +4 -4
  62. flwr/common/address.py +1 -2
  63. flwr/common/args.py +3 -4
  64. flwr/common/config.py +13 -16
  65. flwr/common/constant.py +56 -13
  66. flwr/common/differential_privacy.py +3 -4
  67. flwr/common/event_log_plugin/event_log_plugin.py +3 -4
  68. flwr/common/exit/exit.py +15 -2
  69. flwr/common/exit/exit_code.py +39 -10
  70. flwr/common/exit/exit_handler.py +6 -2
  71. flwr/common/exit/signal_handler.py +5 -5
  72. flwr/common/grpc.py +6 -6
  73. flwr/common/inflatable_protobuf_utils.py +1 -1
  74. flwr/common/inflatable_utils.py +48 -31
  75. flwr/common/logger.py +19 -19
  76. flwr/common/message.py +4 -4
  77. flwr/common/object_ref.py +7 -7
  78. flwr/common/record/array.py +6 -6
  79. flwr/common/record/arrayrecord.py +18 -21
  80. flwr/common/record/configrecord.py +3 -3
  81. flwr/common/record/recorddict.py +5 -5
  82. flwr/common/record/typeddict.py +9 -2
  83. flwr/common/recorddict_compat.py +7 -10
  84. flwr/common/retry_invoker.py +20 -20
  85. flwr/common/secure_aggregation/crypto/symmetric_encryption.py +1 -89
  86. flwr/common/secure_aggregation/ndarrays_arithmetic.py +3 -3
  87. flwr/common/serde.py +9 -6
  88. flwr/common/serde_utils.py +2 -2
  89. flwr/common/telemetry.py +9 -5
  90. flwr/common/typing.py +59 -43
  91. flwr/compat/client/app.py +39 -38
  92. flwr/compat/client/grpc_client/connection.py +13 -13
  93. flwr/compat/server/app.py +5 -6
  94. flwr/proto/appio_pb2.py +13 -3
  95. flwr/proto/appio_pb2.pyi +134 -65
  96. flwr/proto/appio_pb2_grpc.py +20 -0
  97. flwr/proto/appio_pb2_grpc.pyi +27 -0
  98. flwr/proto/clientappio_pb2.py +17 -7
  99. flwr/proto/clientappio_pb2.pyi +15 -0
  100. flwr/proto/clientappio_pb2_grpc.py +206 -40
  101. flwr/proto/clientappio_pb2_grpc.pyi +168 -53
  102. flwr/proto/control_pb2.py +72 -40
  103. flwr/proto/control_pb2.pyi +319 -87
  104. flwr/proto/control_pb2_grpc.py +339 -28
  105. flwr/proto/control_pb2_grpc.pyi +209 -37
  106. flwr/proto/error_pb2.py +13 -3
  107. flwr/proto/error_pb2.pyi +24 -6
  108. flwr/proto/error_pb2_grpc.py +20 -0
  109. flwr/proto/error_pb2_grpc.pyi +27 -0
  110. flwr/proto/fab_pb2.py +24 -10
  111. flwr/proto/fab_pb2.pyi +68 -20
  112. flwr/proto/fab_pb2_grpc.py +20 -0
  113. flwr/proto/fab_pb2_grpc.pyi +27 -0
  114. flwr/proto/federation_pb2.py +38 -0
  115. flwr/proto/federation_pb2.pyi +56 -0
  116. flwr/proto/federation_pb2_grpc.py +24 -0
  117. flwr/proto/federation_pb2_grpc.pyi +31 -0
  118. flwr/proto/fleet_pb2.py +45 -27
  119. flwr/proto/fleet_pb2.pyi +186 -70
  120. flwr/proto/fleet_pb2_grpc.py +277 -66
  121. flwr/proto/fleet_pb2_grpc.pyi +201 -55
  122. flwr/proto/grpcadapter_pb2.py +14 -4
  123. flwr/proto/grpcadapter_pb2.pyi +38 -16
  124. flwr/proto/grpcadapter_pb2_grpc.py +35 -4
  125. flwr/proto/grpcadapter_pb2_grpc.pyi +38 -7
  126. flwr/proto/heartbeat_pb2.py +17 -7
  127. flwr/proto/heartbeat_pb2.pyi +51 -22
  128. flwr/proto/heartbeat_pb2_grpc.py +20 -0
  129. flwr/proto/heartbeat_pb2_grpc.pyi +27 -0
  130. flwr/proto/log_pb2.py +13 -3
  131. flwr/proto/log_pb2.pyi +34 -11
  132. flwr/proto/log_pb2_grpc.py +20 -0
  133. flwr/proto/log_pb2_grpc.pyi +27 -0
  134. flwr/proto/message_pb2.py +15 -5
  135. flwr/proto/message_pb2.pyi +154 -86
  136. flwr/proto/message_pb2_grpc.py +20 -0
  137. flwr/proto/message_pb2_grpc.pyi +27 -0
  138. flwr/proto/node_pb2.py +16 -4
  139. flwr/proto/node_pb2.pyi +77 -4
  140. flwr/proto/node_pb2_grpc.py +20 -0
  141. flwr/proto/node_pb2_grpc.pyi +27 -0
  142. flwr/proto/recorddict_pb2.py +13 -3
  143. flwr/proto/recorddict_pb2.pyi +184 -107
  144. flwr/proto/recorddict_pb2_grpc.py +20 -0
  145. flwr/proto/recorddict_pb2_grpc.pyi +27 -0
  146. flwr/proto/run_pb2.py +40 -31
  147. flwr/proto/run_pb2.pyi +149 -84
  148. flwr/proto/run_pb2_grpc.py +20 -0
  149. flwr/proto/run_pb2_grpc.pyi +27 -0
  150. flwr/proto/serverappio_pb2.py +13 -3
  151. flwr/proto/serverappio_pb2.pyi +32 -8
  152. flwr/proto/serverappio_pb2_grpc.py +246 -65
  153. flwr/proto/serverappio_pb2_grpc.pyi +221 -85
  154. flwr/proto/simulationio_pb2.py +16 -8
  155. flwr/proto/simulationio_pb2.pyi +15 -0
  156. flwr/proto/simulationio_pb2_grpc.py +162 -41
  157. flwr/proto/simulationio_pb2_grpc.pyi +149 -55
  158. flwr/proto/transport_pb2.py +20 -10
  159. flwr/proto/transport_pb2.pyi +249 -160
  160. flwr/proto/transport_pb2_grpc.py +35 -4
  161. flwr/proto/transport_pb2_grpc.pyi +38 -8
  162. flwr/server/app.py +173 -127
  163. flwr/server/client_manager.py +4 -5
  164. flwr/server/client_proxy.py +10 -11
  165. flwr/server/compat/app.py +4 -5
  166. flwr/server/compat/app_utils.py +2 -1
  167. flwr/server/compat/grid_client_proxy.py +10 -12
  168. flwr/server/compat/legacy_context.py +3 -4
  169. flwr/server/fleet_event_log_interceptor.py +2 -1
  170. flwr/server/grid/grid.py +2 -3
  171. flwr/server/grid/grpc_grid.py +10 -8
  172. flwr/server/grid/inmemory_grid.py +4 -4
  173. flwr/server/run_serverapp.py +2 -3
  174. flwr/server/server.py +34 -39
  175. flwr/server/server_app.py +7 -8
  176. flwr/server/server_config.py +1 -2
  177. flwr/server/serverapp/app.py +34 -28
  178. flwr/server/serverapp_components.py +4 -5
  179. flwr/server/strategy/aggregate.py +9 -8
  180. flwr/server/strategy/bulyan.py +13 -11
  181. flwr/server/strategy/dp_adaptive_clipping.py +16 -20
  182. flwr/server/strategy/dp_fixed_clipping.py +12 -17
  183. flwr/server/strategy/dpfedavg_adaptive.py +3 -4
  184. flwr/server/strategy/dpfedavg_fixed.py +6 -10
  185. flwr/server/strategy/fault_tolerant_fedavg.py +14 -13
  186. flwr/server/strategy/fedadagrad.py +18 -14
  187. flwr/server/strategy/fedadam.py +16 -14
  188. flwr/server/strategy/fedavg.py +16 -17
  189. flwr/server/strategy/fedavg_android.py +15 -15
  190. flwr/server/strategy/fedavgm.py +21 -18
  191. flwr/server/strategy/fedmedian.py +2 -3
  192. flwr/server/strategy/fedopt.py +11 -10
  193. flwr/server/strategy/fedprox.py +10 -9
  194. flwr/server/strategy/fedtrimmedavg.py +12 -11
  195. flwr/server/strategy/fedxgb_bagging.py +13 -11
  196. flwr/server/strategy/fedxgb_cyclic.py +6 -6
  197. flwr/server/strategy/fedxgb_nn_avg.py +4 -4
  198. flwr/server/strategy/fedyogi.py +16 -14
  199. flwr/server/strategy/krum.py +12 -11
  200. flwr/server/strategy/qfedavg.py +16 -15
  201. flwr/server/strategy/strategy.py +6 -9
  202. flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +19 -8
  203. flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -2
  204. flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py +3 -4
  205. flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py +10 -12
  206. flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +1 -3
  207. flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +136 -42
  208. flwr/server/superlink/fleet/grpc_rere/{server_interceptor.py → node_auth_server_interceptor.py} +28 -51
  209. flwr/server/superlink/fleet/message_handler/message_handler.py +100 -49
  210. flwr/server/superlink/fleet/rest_rere/rest_api.py +54 -33
  211. flwr/server/superlink/fleet/vce/backend/backend.py +2 -2
  212. flwr/server/superlink/fleet/vce/backend/raybackend.py +6 -6
  213. flwr/server/superlink/fleet/vce/vce_api.py +32 -13
  214. flwr/server/superlink/linkstate/in_memory_linkstate.py +266 -207
  215. flwr/server/superlink/linkstate/linkstate.py +161 -62
  216. flwr/server/superlink/linkstate/linkstate_factory.py +24 -6
  217. flwr/server/superlink/linkstate/sqlite_linkstate.py +698 -638
  218. flwr/server/superlink/linkstate/utils.py +9 -60
  219. flwr/server/superlink/serverappio/serverappio_grpc.py +1 -2
  220. flwr/server/superlink/serverappio/serverappio_servicer.py +28 -23
  221. flwr/server/superlink/simulation/simulationio_grpc.py +1 -2
  222. flwr/server/superlink/simulation/simulationio_servicer.py +19 -14
  223. flwr/server/superlink/utils.py +4 -6
  224. flwr/server/typing.py +1 -1
  225. flwr/server/utils/tensorboard.py +15 -8
  226. flwr/server/utils/validator.py +2 -3
  227. flwr/server/workflow/default_workflows.py +5 -5
  228. flwr/server/workflow/secure_aggregation/secagg_workflow.py +2 -4
  229. flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +12 -10
  230. flwr/serverapp/strategy/bulyan.py +16 -15
  231. flwr/serverapp/strategy/dp_adaptive_clipping.py +12 -11
  232. flwr/serverapp/strategy/dp_fixed_clipping.py +11 -14
  233. flwr/serverapp/strategy/fedadagrad.py +10 -11
  234. flwr/serverapp/strategy/fedadam.py +10 -11
  235. flwr/serverapp/strategy/fedavg.py +9 -10
  236. flwr/serverapp/strategy/fedavgm.py +17 -16
  237. flwr/serverapp/strategy/fedmedian.py +2 -2
  238. flwr/serverapp/strategy/fedopt.py +10 -11
  239. flwr/serverapp/strategy/fedprox.py +7 -8
  240. flwr/serverapp/strategy/fedtrimmedavg.py +9 -9
  241. flwr/serverapp/strategy/fedxgb_bagging.py +3 -3
  242. flwr/serverapp/strategy/fedxgb_cyclic.py +9 -9
  243. flwr/serverapp/strategy/fedyogi.py +9 -11
  244. flwr/serverapp/strategy/krum.py +7 -7
  245. flwr/serverapp/strategy/multikrum.py +9 -9
  246. flwr/serverapp/strategy/qfedavg.py +17 -16
  247. flwr/serverapp/strategy/strategy.py +6 -9
  248. flwr/serverapp/strategy/strategy_utils.py +7 -8
  249. flwr/simulation/app.py +46 -42
  250. flwr/simulation/legacy_app.py +12 -12
  251. flwr/simulation/ray_transport/ray_actor.py +11 -12
  252. flwr/simulation/ray_transport/ray_client_proxy.py +12 -13
  253. flwr/simulation/run_simulation.py +44 -43
  254. flwr/simulation/simulationio_connection.py +4 -4
  255. flwr/supercore/cli/flower_superexec.py +3 -4
  256. flwr/supercore/constant.py +52 -0
  257. flwr/supercore/corestate/corestate.py +24 -3
  258. flwr/supercore/corestate/in_memory_corestate.py +138 -0
  259. flwr/supercore/corestate/sqlite_corestate.py +157 -0
  260. flwr/supercore/ffs/disk_ffs.py +1 -2
  261. flwr/supercore/ffs/ffs.py +1 -2
  262. flwr/supercore/ffs/ffs_factory.py +1 -2
  263. flwr/{common → supercore}/heartbeat.py +20 -25
  264. flwr/supercore/object_store/in_memory_object_store.py +1 -6
  265. flwr/supercore/object_store/object_store.py +1 -2
  266. flwr/supercore/object_store/object_store_factory.py +27 -8
  267. flwr/supercore/object_store/sqlite_object_store.py +253 -0
  268. flwr/{client/clientapp → supercore/primitives}/__init__.py +1 -1
  269. flwr/supercore/primitives/asymmetric.py +117 -0
  270. flwr/supercore/primitives/asymmetric_ed25519.py +175 -0
  271. flwr/supercore/sqlite_mixin.py +159 -0
  272. flwr/supercore/superexec/plugin/base_exec_plugin.py +1 -2
  273. flwr/supercore/superexec/plugin/exec_plugin.py +3 -3
  274. flwr/supercore/superexec/run_superexec.py +9 -13
  275. flwr/supercore/utils.py +20 -0
  276. flwr/superlink/artifact_provider/artifact_provider.py +1 -2
  277. flwr/{common → superlink}/auth_plugin/__init__.py +6 -6
  278. flwr/superlink/auth_plugin/auth_plugin.py +88 -0
  279. flwr/superlink/auth_plugin/noop_auth_plugin.py +84 -0
  280. flwr/superlink/federation/__init__.py +24 -0
  281. flwr/superlink/federation/federation_manager.py +64 -0
  282. flwr/superlink/federation/noop_federation_manager.py +71 -0
  283. flwr/superlink/servicer/control/{control_user_auth_interceptor.py → control_account_auth_interceptor.py} +41 -32
  284. flwr/superlink/servicer/control/control_event_log_interceptor.py +7 -7
  285. flwr/superlink/servicer/control/control_grpc.py +18 -17
  286. flwr/superlink/servicer/control/control_license_interceptor.py +3 -3
  287. flwr/superlink/servicer/control/control_servicer.py +239 -63
  288. flwr/supernode/cli/flower_supernode.py +74 -26
  289. flwr/supernode/nodestate/in_memory_nodestate.py +60 -49
  290. flwr/supernode/nodestate/nodestate.py +7 -8
  291. flwr/supernode/nodestate/nodestate_factory.py +7 -4
  292. flwr/supernode/runtime/run_clientapp.py +43 -24
  293. flwr/supernode/servicer/clientappio/clientappio_servicer.py +40 -10
  294. flwr/supernode/start_client_internal.py +175 -51
  295. {flwr-1.22.0.dist-info → flwr-1.24.0.dist-info}/METADATA +8 -8
  296. flwr-1.24.0.dist-info/RECORD +454 -0
  297. flwr/common/auth_plugin/auth_plugin.py +0 -149
  298. flwr/supercore/object_store/utils.py +0 -43
  299. flwr-1.22.0.dist-info/RECORD +0 -428
  300. {flwr-1.22.0.dist-info → flwr-1.24.0.dist-info}/WHEEL +0 -0
  301. {flwr-1.22.0.dist-info → flwr-1.24.0.dist-info}/entry_points.txt +0 -0
@@ -12,20 +12,29 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
  # ==============================================================================
15
- """Flower user auth plugins."""
15
+ """Flower account auth plugins."""
16
16
 
17
17
 
18
- from flwr.common.auth_plugin import CliAuthPlugin
19
- from flwr.common.constant import AuthType
18
+ from flwr.common.constant import AuthnType
20
19
 
20
+ from .auth_plugin import CliAuthPlugin, LoginError
21
+ from .noop_auth_plugin import NoOpCliAuthPlugin
21
22
  from .oidc_cli_plugin import OidcCliPlugin
22
23
 
23
24
 
24
- def get_cli_auth_plugins() -> dict[str, type[CliAuthPlugin]]:
25
+ def get_cli_plugin_class(authn_type: str) -> type[CliAuthPlugin]:
25
26
  """Return all CLI authentication plugins."""
26
- return {AuthType.OIDC: OidcCliPlugin}
27
+ if authn_type == AuthnType.NOOP:
28
+ return NoOpCliAuthPlugin
29
+ if authn_type == AuthnType.OIDC:
30
+ return OidcCliPlugin
31
+ raise ValueError(f"Unsupported authentication type: {authn_type}")
27
32
 
28
33
 
29
34
  __all__ = [
30
- "get_cli_auth_plugins",
35
+ "CliAuthPlugin",
36
+ "LoginError",
37
+ "NoOpCliAuthPlugin",
38
+ "OidcCliPlugin",
39
+ "get_cli_plugin_class",
31
40
  ]
@@ -0,0 +1,94 @@
1
+ # Copyright 2025 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 account auth plugin."""
16
+
17
+
18
+ from abc import ABC, abstractmethod
19
+ from collections.abc import Sequence
20
+ from pathlib import Path
21
+
22
+ from flwr.common.typing import AccountAuthCredentials, AccountAuthLoginDetails
23
+ from flwr.proto.control_pb2_grpc import ControlStub
24
+
25
+
26
+ class LoginError(Exception):
27
+ """Login error exception."""
28
+
29
+ def __init__(self, message: str):
30
+ self.message = message
31
+
32
+
33
+ class CliAuthPlugin(ABC):
34
+ """Abstract Flower Auth Plugin class for CLI.
35
+
36
+ Parameters
37
+ ----------
38
+ credentials_path : Path
39
+ Path to the Flower account's authentication credentials file.
40
+ """
41
+
42
+ @staticmethod
43
+ @abstractmethod
44
+ def login(
45
+ login_details: AccountAuthLoginDetails,
46
+ control_stub: ControlStub,
47
+ ) -> AccountAuthCredentials:
48
+ """Authenticate the account and retrieve authentication credentials.
49
+
50
+ Parameters
51
+ ----------
52
+ login_details : AccountAuthLoginDetails
53
+ An object containing the account's login details.
54
+ control_stub : ControlStub
55
+ A stub for executing RPC calls to the server.
56
+
57
+ Returns
58
+ -------
59
+ AccountAuthCredentials
60
+ The authentication credentials obtained after login.
61
+
62
+ Raises
63
+ ------
64
+ LoginError
65
+ If the login process fails.
66
+ """
67
+
68
+ @abstractmethod
69
+ def __init__(self, credentials_path: Path):
70
+ """Abstract constructor."""
71
+
72
+ @abstractmethod
73
+ def store_tokens(self, credentials: AccountAuthCredentials) -> None:
74
+ """Store authentication tokens to the `credentials_path`.
75
+
76
+ The credentials, including tokens, will be saved as a JSON file
77
+ at `credentials_path`.
78
+ """
79
+
80
+ @abstractmethod
81
+ def load_tokens(self) -> None:
82
+ """Load authentication tokens from the `credentials_path`."""
83
+
84
+ @abstractmethod
85
+ def write_tokens_to_metadata(
86
+ self, metadata: Sequence[tuple[str, str | bytes]]
87
+ ) -> Sequence[tuple[str, str | bytes]]:
88
+ """Write authentication tokens to the provided metadata."""
89
+
90
+ @abstractmethod
91
+ def read_tokens_from_metadata(
92
+ self, metadata: Sequence[tuple[str, str | bytes]]
93
+ ) -> AccountAuthCredentials | None:
94
+ """Read authentication tokens from the provided metadata."""
@@ -0,0 +1,101 @@
1
+ # Copyright 2025 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
+ """Concrete NoOp implementation for CLI-side account authentication plugin."""
16
+
17
+
18
+ from collections.abc import Sequence
19
+ from pathlib import Path
20
+
21
+ from flwr.common.typing import AccountAuthCredentials, AccountAuthLoginDetails
22
+ from flwr.proto.control_pb2_grpc import ControlStub
23
+
24
+ from .auth_plugin import CliAuthPlugin, LoginError
25
+
26
+
27
+ class NoOpCliAuthPlugin(CliAuthPlugin):
28
+ """No-operation implementation of the CliAuthPlugin.
29
+
30
+ This plugin is used when account authentication is not enabled. It provides stub
31
+ implementations of all authentication methods that perform no actions.
32
+ """
33
+
34
+ @staticmethod
35
+ def login(
36
+ login_details: AccountAuthLoginDetails,
37
+ control_stub: ControlStub,
38
+ ) -> AccountAuthCredentials:
39
+ """Raise LoginError as no-op plugin does not support login.
40
+
41
+ Parameters
42
+ ----------
43
+ login_details : AccountAuthLoginDetails
44
+ Login details (unused).
45
+ control_stub : ControlStub
46
+ Control stub (unused).
47
+
48
+ Returns
49
+ -------
50
+ AccountAuthCredentials
51
+ This method never returns as it always raises an exception.
52
+
53
+ Raises
54
+ ------
55
+ LoginError
56
+ Always raised to indicate authentication is not enabled.
57
+ """
58
+ raise LoginError("Account authentication is not enabled on this SuperLink.")
59
+
60
+ def __init__(self, credentials_path: Path) -> None:
61
+ pass
62
+
63
+ def store_tokens(self, credentials: AccountAuthCredentials) -> None:
64
+ """Do nothing (no-op implementation)."""
65
+
66
+ def load_tokens(self) -> None:
67
+ """Do nothing (no-op implementation)."""
68
+
69
+ def write_tokens_to_metadata(
70
+ self, metadata: Sequence[tuple[str, str | bytes]]
71
+ ) -> Sequence[tuple[str, str | bytes]]:
72
+ """Return the metadata unchanged.
73
+
74
+ Parameters
75
+ ----------
76
+ metadata : Sequence[tuple[str, str | bytes]]
77
+ The original metadata.
78
+
79
+ Returns
80
+ -------
81
+ Sequence[tuple[str, str | bytes]]
82
+ The same metadata, unmodified.
83
+ """
84
+ return metadata
85
+
86
+ def read_tokens_from_metadata(
87
+ self, metadata: Sequence[tuple[str, str | bytes]]
88
+ ) -> AccountAuthCredentials | None:
89
+ """Return None (no tokens to read).
90
+
91
+ Parameters
92
+ ----------
93
+ metadata : Sequence[tuple[str, str | bytes]]
94
+ The metadata to read from (unused).
95
+
96
+ Returns
97
+ -------
98
+ None
99
+ Always returns None as no authentication is performed.
100
+ """
101
+ return None
@@ -12,48 +12,71 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
  # ==============================================================================
15
- """Flower CLI user auth plugin for OIDC."""
15
+ """Flower CLI account auth plugin for OIDC."""
16
16
 
17
17
 
18
18
  import json
19
19
  import time
20
20
  from collections.abc import Sequence
21
21
  from pathlib import Path
22
- from typing import Any, Optional, Union
22
+ from typing import Any
23
23
 
24
24
  import typer
25
25
 
26
- from flwr.common.auth_plugin import CliAuthPlugin
27
26
  from flwr.common.constant import (
28
27
  ACCESS_TOKEN_KEY,
29
- AUTH_TYPE_JSON_KEY,
28
+ AUTHN_TYPE_JSON_KEY,
30
29
  REFRESH_TOKEN_KEY,
31
- AuthType,
30
+ AuthnType,
32
31
  )
33
- from flwr.common.typing import UserAuthCredentials, UserAuthLoginDetails
32
+ from flwr.common.typing import AccountAuthCredentials, AccountAuthLoginDetails
34
33
  from flwr.proto.control_pb2 import ( # pylint: disable=E0611
35
34
  GetAuthTokensRequest,
36
35
  GetAuthTokensResponse,
37
36
  )
38
37
  from flwr.proto.control_pb2_grpc import ControlStub
39
38
 
39
+ from .auth_plugin import CliAuthPlugin, LoginError
40
+
40
41
 
41
42
  class OidcCliPlugin(CliAuthPlugin):
42
- """Flower OIDC auth plugin for CLI."""
43
+ """Flower OIDC authentication plugin for CLI.
44
+
45
+ This plugin implements OpenID Connect (OIDC) device flow authentication for CLI
46
+ access to Flower SuperLink.
47
+ """
43
48
 
44
49
  def __init__(self, credentials_path: Path):
45
- self.access_token: Optional[str] = None
46
- self.refresh_token: Optional[str] = None
50
+ self.access_token: str | None = None
51
+ self.refresh_token: str | None = None
47
52
  self.credentials_path = credentials_path
48
53
 
49
54
  @staticmethod
50
55
  def login(
51
- login_details: UserAuthLoginDetails,
56
+ login_details: AccountAuthLoginDetails,
52
57
  control_stub: ControlStub,
53
- ) -> UserAuthCredentials:
54
- """Authenticate the user and retrieve authentication credentials."""
58
+ ) -> AccountAuthCredentials:
59
+ """Authenticate the account and retrieve authentication credentials.
60
+
61
+ Parameters
62
+ ----------
63
+ login_details : AccountAuthLoginDetails
64
+ Login details containing device code and verification URI.
65
+ control_stub : ControlStub
66
+ Control stub for making authentication requests.
67
+
68
+ Returns
69
+ -------
70
+ AccountAuthCredentials
71
+ The access and refresh tokens.
72
+
73
+ Raises
74
+ ------
75
+ LoginError
76
+ If authentication times out.
77
+ """
55
78
  typer.secho(
56
- "Please login with your user credentials here: "
79
+ "Please log into your Flower account here: "
57
80
  f"{login_details.verification_uri_complete}",
58
81
  fg=typer.colors.BLUE,
59
82
  )
@@ -69,26 +92,16 @@ class OidcCliPlugin(CliAuthPlugin):
69
92
  refresh_token = res.refresh_token
70
93
 
71
94
  if access_token and refresh_token:
72
- typer.secho(
73
- "✅ Login successful.",
74
- fg=typer.colors.GREEN,
75
- bold=False,
76
- )
77
- return UserAuthCredentials(
95
+ return AccountAuthCredentials(
78
96
  access_token=access_token,
79
97
  refresh_token=refresh_token,
80
98
  )
81
99
 
82
100
  time.sleep(login_details.interval)
83
101
 
84
- typer.secho(
85
- "❌ Timeout, failed to sign in.",
86
- fg=typer.colors.RED,
87
- bold=True,
88
- )
89
- raise typer.Exit(code=1)
102
+ raise LoginError("Process timed out.")
90
103
 
91
- def store_tokens(self, credentials: UserAuthCredentials) -> None:
104
+ def store_tokens(self, credentials: AccountAuthCredentials) -> None:
92
105
  """Store authentication tokens to the `credentials_path`.
93
106
 
94
107
  The credentials, including tokens, will be saved as a JSON file
@@ -97,7 +110,7 @@ class OidcCliPlugin(CliAuthPlugin):
97
110
  self.access_token = credentials.access_token
98
111
  self.refresh_token = credentials.refresh_token
99
112
  json_dict = {
100
- AUTH_TYPE_JSON_KEY: AuthType.OIDC,
113
+ AUTHN_TYPE_JSON_KEY: AuthnType.OIDC,
101
114
  ACCESS_TOKEN_KEY: credentials.access_token,
102
115
  REFRESH_TOKEN_KEY: credentials.refresh_token,
103
116
  }
@@ -117,14 +130,15 @@ class OidcCliPlugin(CliAuthPlugin):
117
130
  self.refresh_token = refresh_token
118
131
 
119
132
  def write_tokens_to_metadata(
120
- self, metadata: Sequence[tuple[str, Union[str, bytes]]]
121
- ) -> Sequence[tuple[str, Union[str, bytes]]]:
133
+ self, metadata: Sequence[tuple[str, str | bytes]]
134
+ ) -> Sequence[tuple[str, str | bytes]]:
122
135
  """Write authentication tokens to the provided metadata."""
123
136
  if self.access_token is None or self.refresh_token is None:
124
137
  typer.secho(
125
138
  "❌ Missing authentication tokens. Please login first.",
126
139
  fg=typer.colors.RED,
127
140
  bold=True,
141
+ err=True,
128
142
  )
129
143
  raise typer.Exit(code=1)
130
144
 
@@ -134,15 +148,15 @@ class OidcCliPlugin(CliAuthPlugin):
134
148
  ]
135
149
 
136
150
  def read_tokens_from_metadata(
137
- self, metadata: Sequence[tuple[str, Union[str, bytes]]]
138
- ) -> Optional[UserAuthCredentials]:
151
+ self, metadata: Sequence[tuple[str, str | bytes]]
152
+ ) -> AccountAuthCredentials | None:
139
153
  """Read authentication tokens from the provided metadata."""
140
154
  metadata_dict = dict(metadata)
141
155
  access_token = metadata_dict.get(ACCESS_TOKEN_KEY)
142
156
  refresh_token = metadata_dict.get(REFRESH_TOKEN_KEY)
143
157
 
144
158
  if isinstance(access_token, str) and isinstance(refresh_token, str):
145
- return UserAuthCredentials(
159
+ return AccountAuthCredentials(
146
160
  access_token=access_token,
147
161
  refresh_token=refresh_token,
148
162
  )