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.
- flwr/__init__.py +16 -5
- flwr/app/error.py +2 -2
- flwr/app/exception.py +3 -3
- flwr/cli/app.py +34 -1
- flwr/cli/app_cmd/__init__.py +23 -0
- flwr/cli/app_cmd/publish.py +285 -0
- flwr/cli/app_cmd/review.py +252 -0
- flwr/cli/auth_plugin/__init__.py +15 -6
- flwr/cli/auth_plugin/auth_plugin.py +94 -0
- flwr/cli/auth_plugin/noop_auth_plugin.py +101 -0
- flwr/cli/auth_plugin/oidc_cli_plugin.py +46 -32
- flwr/cli/build.py +166 -53
- flwr/cli/{cli_user_auth_interceptor.py → cli_account_auth_interceptor.py} +29 -11
- flwr/cli/config_utils.py +101 -13
- flwr/cli/federation/__init__.py +24 -0
- flwr/cli/federation/ls.py +140 -0
- flwr/cli/federation/show.py +317 -0
- flwr/cli/install.py +91 -13
- flwr/cli/log.py +54 -11
- flwr/cli/login/login.py +41 -27
- flwr/cli/ls.py +177 -133
- flwr/cli/new/new.py +175 -40
- flwr/cli/new/templates/app/code/task.pytorch.py.tpl +1 -0
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +3 -3
- flwr/cli/new/templates/app/pyproject.pytorch_legacy_api.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +2 -2
- flwr/cli/new/templates/app/pyproject.xgboost.toml.tpl +1 -1
- flwr/cli/pull.py +12 -7
- flwr/cli/run/run.py +82 -31
- flwr/cli/run_utils.py +130 -0
- flwr/cli/stop.py +27 -9
- flwr/cli/supernode/__init__.py +25 -0
- flwr/cli/supernode/ls.py +268 -0
- flwr/cli/supernode/register.py +190 -0
- flwr/cli/supernode/unregister.py +140 -0
- flwr/cli/utils.py +464 -81
- flwr/client/__init__.py +2 -1
- flwr/client/dpfedavg_numpy_client.py +4 -1
- flwr/client/grpc_adapter_client/connection.py +12 -15
- flwr/client/grpc_rere_client/connection.py +68 -41
- flwr/client/grpc_rere_client/grpc_adapter.py +34 -14
- flwr/client/grpc_rere_client/{client_interceptor.py → node_auth_client_interceptor.py} +5 -7
- flwr/client/message_handler/message_handler.py +2 -2
- flwr/client/mod/secure_aggregation/secaggplus_mod.py +10 -8
- flwr/client/numpy_client.py +1 -1
- flwr/client/rest_client/connection.py +94 -51
- flwr/client/run_info_store.py +4 -5
- flwr/client/typing.py +1 -1
- flwr/clientapp/__init__.py +1 -2
- flwr/{client → clientapp}/client_app.py +9 -10
- flwr/clientapp/mod/centraldp_mods.py +16 -17
- flwr/clientapp/mod/localdp_mod.py +8 -9
- flwr/clientapp/typing.py +1 -1
- flwr/{client/clientapp → clientapp}/utils.py +4 -4
- flwr/common/address.py +1 -2
- flwr/common/args.py +3 -4
- flwr/common/config.py +13 -16
- flwr/common/constant.py +56 -13
- flwr/common/differential_privacy.py +3 -4
- flwr/common/event_log_plugin/event_log_plugin.py +3 -4
- flwr/common/exit/exit.py +15 -2
- flwr/common/exit/exit_code.py +39 -10
- flwr/common/exit/exit_handler.py +6 -2
- flwr/common/exit/signal_handler.py +5 -5
- flwr/common/grpc.py +6 -6
- flwr/common/inflatable_protobuf_utils.py +1 -1
- flwr/common/inflatable_utils.py +48 -31
- flwr/common/logger.py +19 -19
- flwr/common/message.py +4 -4
- flwr/common/object_ref.py +7 -7
- flwr/common/record/array.py +6 -6
- flwr/common/record/arrayrecord.py +18 -21
- flwr/common/record/configrecord.py +3 -3
- flwr/common/record/recorddict.py +5 -5
- flwr/common/record/typeddict.py +9 -2
- flwr/common/recorddict_compat.py +7 -10
- flwr/common/retry_invoker.py +20 -20
- flwr/common/secure_aggregation/crypto/symmetric_encryption.py +1 -89
- flwr/common/secure_aggregation/ndarrays_arithmetic.py +3 -3
- flwr/common/serde.py +9 -6
- flwr/common/serde_utils.py +2 -2
- flwr/common/telemetry.py +9 -5
- flwr/common/typing.py +59 -43
- flwr/compat/client/app.py +39 -38
- flwr/compat/client/grpc_client/connection.py +13 -13
- flwr/compat/server/app.py +5 -6
- flwr/proto/appio_pb2.py +13 -3
- flwr/proto/appio_pb2.pyi +134 -65
- flwr/proto/appio_pb2_grpc.py +20 -0
- flwr/proto/appio_pb2_grpc.pyi +27 -0
- flwr/proto/clientappio_pb2.py +17 -7
- flwr/proto/clientappio_pb2.pyi +15 -0
- flwr/proto/clientappio_pb2_grpc.py +206 -40
- flwr/proto/clientappio_pb2_grpc.pyi +168 -53
- flwr/proto/control_pb2.py +72 -40
- flwr/proto/control_pb2.pyi +319 -87
- flwr/proto/control_pb2_grpc.py +339 -28
- flwr/proto/control_pb2_grpc.pyi +209 -37
- flwr/proto/error_pb2.py +13 -3
- flwr/proto/error_pb2.pyi +24 -6
- flwr/proto/error_pb2_grpc.py +20 -0
- flwr/proto/error_pb2_grpc.pyi +27 -0
- flwr/proto/fab_pb2.py +24 -10
- flwr/proto/fab_pb2.pyi +68 -20
- flwr/proto/fab_pb2_grpc.py +20 -0
- flwr/proto/fab_pb2_grpc.pyi +27 -0
- flwr/proto/federation_pb2.py +38 -0
- flwr/proto/federation_pb2.pyi +56 -0
- flwr/proto/federation_pb2_grpc.py +24 -0
- flwr/proto/federation_pb2_grpc.pyi +31 -0
- flwr/proto/fleet_pb2.py +45 -27
- flwr/proto/fleet_pb2.pyi +186 -70
- flwr/proto/fleet_pb2_grpc.py +277 -66
- flwr/proto/fleet_pb2_grpc.pyi +201 -55
- flwr/proto/grpcadapter_pb2.py +14 -4
- flwr/proto/grpcadapter_pb2.pyi +38 -16
- flwr/proto/grpcadapter_pb2_grpc.py +35 -4
- flwr/proto/grpcadapter_pb2_grpc.pyi +38 -7
- flwr/proto/heartbeat_pb2.py +17 -7
- flwr/proto/heartbeat_pb2.pyi +51 -22
- flwr/proto/heartbeat_pb2_grpc.py +20 -0
- flwr/proto/heartbeat_pb2_grpc.pyi +27 -0
- flwr/proto/log_pb2.py +13 -3
- flwr/proto/log_pb2.pyi +34 -11
- flwr/proto/log_pb2_grpc.py +20 -0
- flwr/proto/log_pb2_grpc.pyi +27 -0
- flwr/proto/message_pb2.py +15 -5
- flwr/proto/message_pb2.pyi +154 -86
- flwr/proto/message_pb2_grpc.py +20 -0
- flwr/proto/message_pb2_grpc.pyi +27 -0
- flwr/proto/node_pb2.py +16 -4
- flwr/proto/node_pb2.pyi +77 -4
- flwr/proto/node_pb2_grpc.py +20 -0
- flwr/proto/node_pb2_grpc.pyi +27 -0
- flwr/proto/recorddict_pb2.py +13 -3
- flwr/proto/recorddict_pb2.pyi +184 -107
- flwr/proto/recorddict_pb2_grpc.py +20 -0
- flwr/proto/recorddict_pb2_grpc.pyi +27 -0
- flwr/proto/run_pb2.py +40 -31
- flwr/proto/run_pb2.pyi +149 -84
- flwr/proto/run_pb2_grpc.py +20 -0
- flwr/proto/run_pb2_grpc.pyi +27 -0
- flwr/proto/serverappio_pb2.py +13 -3
- flwr/proto/serverappio_pb2.pyi +32 -8
- flwr/proto/serverappio_pb2_grpc.py +246 -65
- flwr/proto/serverappio_pb2_grpc.pyi +221 -85
- flwr/proto/simulationio_pb2.py +16 -8
- flwr/proto/simulationio_pb2.pyi +15 -0
- flwr/proto/simulationio_pb2_grpc.py +162 -41
- flwr/proto/simulationio_pb2_grpc.pyi +149 -55
- flwr/proto/transport_pb2.py +20 -10
- flwr/proto/transport_pb2.pyi +249 -160
- flwr/proto/transport_pb2_grpc.py +35 -4
- flwr/proto/transport_pb2_grpc.pyi +38 -8
- flwr/server/app.py +173 -127
- flwr/server/client_manager.py +4 -5
- flwr/server/client_proxy.py +10 -11
- flwr/server/compat/app.py +4 -5
- flwr/server/compat/app_utils.py +2 -1
- flwr/server/compat/grid_client_proxy.py +10 -12
- flwr/server/compat/legacy_context.py +3 -4
- flwr/server/fleet_event_log_interceptor.py +2 -1
- flwr/server/grid/grid.py +2 -3
- flwr/server/grid/grpc_grid.py +10 -8
- flwr/server/grid/inmemory_grid.py +4 -4
- flwr/server/run_serverapp.py +2 -3
- flwr/server/server.py +34 -39
- flwr/server/server_app.py +7 -8
- flwr/server/server_config.py +1 -2
- flwr/server/serverapp/app.py +34 -28
- flwr/server/serverapp_components.py +4 -5
- flwr/server/strategy/aggregate.py +9 -8
- flwr/server/strategy/bulyan.py +13 -11
- flwr/server/strategy/dp_adaptive_clipping.py +16 -20
- flwr/server/strategy/dp_fixed_clipping.py +12 -17
- flwr/server/strategy/dpfedavg_adaptive.py +3 -4
- flwr/server/strategy/dpfedavg_fixed.py +6 -10
- flwr/server/strategy/fault_tolerant_fedavg.py +14 -13
- flwr/server/strategy/fedadagrad.py +18 -14
- flwr/server/strategy/fedadam.py +16 -14
- flwr/server/strategy/fedavg.py +16 -17
- flwr/server/strategy/fedavg_android.py +15 -15
- flwr/server/strategy/fedavgm.py +21 -18
- flwr/server/strategy/fedmedian.py +2 -3
- flwr/server/strategy/fedopt.py +11 -10
- flwr/server/strategy/fedprox.py +10 -9
- flwr/server/strategy/fedtrimmedavg.py +12 -11
- flwr/server/strategy/fedxgb_bagging.py +13 -11
- flwr/server/strategy/fedxgb_cyclic.py +6 -6
- flwr/server/strategy/fedxgb_nn_avg.py +4 -4
- flwr/server/strategy/fedyogi.py +16 -14
- flwr/server/strategy/krum.py +12 -11
- flwr/server/strategy/qfedavg.py +16 -15
- flwr/server/strategy/strategy.py +6 -9
- flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +19 -8
- flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -2
- flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py +3 -4
- flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py +10 -12
- flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +1 -3
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +136 -42
- flwr/server/superlink/fleet/grpc_rere/{server_interceptor.py → node_auth_server_interceptor.py} +28 -51
- flwr/server/superlink/fleet/message_handler/message_handler.py +100 -49
- flwr/server/superlink/fleet/rest_rere/rest_api.py +54 -33
- flwr/server/superlink/fleet/vce/backend/backend.py +2 -2
- flwr/server/superlink/fleet/vce/backend/raybackend.py +6 -6
- flwr/server/superlink/fleet/vce/vce_api.py +32 -13
- flwr/server/superlink/linkstate/in_memory_linkstate.py +266 -207
- flwr/server/superlink/linkstate/linkstate.py +161 -62
- flwr/server/superlink/linkstate/linkstate_factory.py +24 -6
- flwr/server/superlink/linkstate/sqlite_linkstate.py +698 -638
- flwr/server/superlink/linkstate/utils.py +9 -60
- flwr/server/superlink/serverappio/serverappio_grpc.py +1 -2
- flwr/server/superlink/serverappio/serverappio_servicer.py +28 -23
- flwr/server/superlink/simulation/simulationio_grpc.py +1 -2
- flwr/server/superlink/simulation/simulationio_servicer.py +19 -14
- flwr/server/superlink/utils.py +4 -6
- flwr/server/typing.py +1 -1
- flwr/server/utils/tensorboard.py +15 -8
- flwr/server/utils/validator.py +2 -3
- flwr/server/workflow/default_workflows.py +5 -5
- flwr/server/workflow/secure_aggregation/secagg_workflow.py +2 -4
- flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +12 -10
- flwr/serverapp/strategy/bulyan.py +16 -15
- flwr/serverapp/strategy/dp_adaptive_clipping.py +12 -11
- flwr/serverapp/strategy/dp_fixed_clipping.py +11 -14
- flwr/serverapp/strategy/fedadagrad.py +10 -11
- flwr/serverapp/strategy/fedadam.py +10 -11
- flwr/serverapp/strategy/fedavg.py +9 -10
- flwr/serverapp/strategy/fedavgm.py +17 -16
- flwr/serverapp/strategy/fedmedian.py +2 -2
- flwr/serverapp/strategy/fedopt.py +10 -11
- flwr/serverapp/strategy/fedprox.py +7 -8
- flwr/serverapp/strategy/fedtrimmedavg.py +9 -9
- flwr/serverapp/strategy/fedxgb_bagging.py +3 -3
- flwr/serverapp/strategy/fedxgb_cyclic.py +9 -9
- flwr/serverapp/strategy/fedyogi.py +9 -11
- flwr/serverapp/strategy/krum.py +7 -7
- flwr/serverapp/strategy/multikrum.py +9 -9
- flwr/serverapp/strategy/qfedavg.py +17 -16
- flwr/serverapp/strategy/strategy.py +6 -9
- flwr/serverapp/strategy/strategy_utils.py +7 -8
- flwr/simulation/app.py +46 -42
- flwr/simulation/legacy_app.py +12 -12
- flwr/simulation/ray_transport/ray_actor.py +11 -12
- flwr/simulation/ray_transport/ray_client_proxy.py +12 -13
- flwr/simulation/run_simulation.py +44 -43
- flwr/simulation/simulationio_connection.py +4 -4
- flwr/supercore/cli/flower_superexec.py +3 -4
- flwr/supercore/constant.py +52 -0
- flwr/supercore/corestate/corestate.py +24 -3
- flwr/supercore/corestate/in_memory_corestate.py +138 -0
- flwr/supercore/corestate/sqlite_corestate.py +157 -0
- flwr/supercore/ffs/disk_ffs.py +1 -2
- flwr/supercore/ffs/ffs.py +1 -2
- flwr/supercore/ffs/ffs_factory.py +1 -2
- flwr/{common → supercore}/heartbeat.py +20 -25
- flwr/supercore/object_store/in_memory_object_store.py +1 -6
- flwr/supercore/object_store/object_store.py +1 -2
- flwr/supercore/object_store/object_store_factory.py +27 -8
- flwr/supercore/object_store/sqlite_object_store.py +253 -0
- flwr/{client/clientapp → supercore/primitives}/__init__.py +1 -1
- flwr/supercore/primitives/asymmetric.py +117 -0
- flwr/supercore/primitives/asymmetric_ed25519.py +175 -0
- flwr/supercore/sqlite_mixin.py +159 -0
- flwr/supercore/superexec/plugin/base_exec_plugin.py +1 -2
- flwr/supercore/superexec/plugin/exec_plugin.py +3 -3
- flwr/supercore/superexec/run_superexec.py +9 -13
- flwr/supercore/utils.py +20 -0
- flwr/superlink/artifact_provider/artifact_provider.py +1 -2
- flwr/{common → superlink}/auth_plugin/__init__.py +6 -6
- flwr/superlink/auth_plugin/auth_plugin.py +88 -0
- flwr/superlink/auth_plugin/noop_auth_plugin.py +84 -0
- flwr/superlink/federation/__init__.py +24 -0
- flwr/superlink/federation/federation_manager.py +64 -0
- flwr/superlink/federation/noop_federation_manager.py +71 -0
- flwr/superlink/servicer/control/{control_user_auth_interceptor.py → control_account_auth_interceptor.py} +41 -32
- flwr/superlink/servicer/control/control_event_log_interceptor.py +7 -7
- flwr/superlink/servicer/control/control_grpc.py +18 -17
- flwr/superlink/servicer/control/control_license_interceptor.py +3 -3
- flwr/superlink/servicer/control/control_servicer.py +239 -63
- flwr/supernode/cli/flower_supernode.py +74 -26
- flwr/supernode/nodestate/in_memory_nodestate.py +60 -49
- flwr/supernode/nodestate/nodestate.py +7 -8
- flwr/supernode/nodestate/nodestate_factory.py +7 -4
- flwr/supernode/runtime/run_clientapp.py +43 -24
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +40 -10
- flwr/supernode/start_client_internal.py +175 -51
- {flwr-1.22.0.dist-info → flwr-1.24.0.dist-info}/METADATA +8 -8
- flwr-1.24.0.dist-info/RECORD +454 -0
- flwr/common/auth_plugin/auth_plugin.py +0 -149
- flwr/supercore/object_store/utils.py +0 -43
- flwr-1.22.0.dist-info/RECORD +0 -428
- {flwr-1.22.0.dist-info → flwr-1.24.0.dist-info}/WHEEL +0 -0
- {flwr-1.22.0.dist-info → flwr-1.24.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,117 @@
|
|
|
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
|
+
"""Asymmetric cryptography utilities."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from typing import cast
|
|
19
|
+
|
|
20
|
+
from cryptography.exceptions import InvalidSignature
|
|
21
|
+
from cryptography.hazmat.primitives import hashes, serialization
|
|
22
|
+
from cryptography.hazmat.primitives.asymmetric import ec
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def generate_key_pairs() -> (
|
|
26
|
+
tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]
|
|
27
|
+
):
|
|
28
|
+
"""Generate private and public key pairs with Cryptography."""
|
|
29
|
+
private_key = ec.generate_private_key(ec.SECP384R1())
|
|
30
|
+
public_key = private_key.public_key()
|
|
31
|
+
return private_key, public_key
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def private_key_to_bytes(private_key: ec.EllipticCurvePrivateKey) -> bytes:
|
|
35
|
+
"""Serialize private key to bytes."""
|
|
36
|
+
return private_key.private_bytes(
|
|
37
|
+
encoding=serialization.Encoding.PEM,
|
|
38
|
+
format=serialization.PrivateFormat.PKCS8,
|
|
39
|
+
encryption_algorithm=serialization.NoEncryption(),
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def bytes_to_private_key(private_key_bytes: bytes) -> ec.EllipticCurvePrivateKey:
|
|
44
|
+
"""Deserialize private key from bytes."""
|
|
45
|
+
return cast(
|
|
46
|
+
ec.EllipticCurvePrivateKey,
|
|
47
|
+
serialization.load_pem_private_key(data=private_key_bytes, password=None),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def public_key_to_bytes(public_key: ec.EllipticCurvePublicKey) -> bytes:
|
|
52
|
+
"""Serialize public key to bytes."""
|
|
53
|
+
return public_key.public_bytes(
|
|
54
|
+
encoding=serialization.Encoding.PEM,
|
|
55
|
+
format=serialization.PublicFormat.SubjectPublicKeyInfo,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def bytes_to_public_key(public_key_bytes: bytes) -> ec.EllipticCurvePublicKey:
|
|
60
|
+
"""Deserialize public key from bytes."""
|
|
61
|
+
return cast(
|
|
62
|
+
ec.EllipticCurvePublicKey,
|
|
63
|
+
serialization.load_pem_public_key(data=public_key_bytes),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def sign_message(private_key: ec.EllipticCurvePrivateKey, message: bytes) -> bytes:
|
|
68
|
+
"""Sign a message using the provided EC private key.
|
|
69
|
+
|
|
70
|
+
Parameters
|
|
71
|
+
----------
|
|
72
|
+
private_key : ec.EllipticCurvePrivateKey
|
|
73
|
+
The EC private key to sign the message with.
|
|
74
|
+
message : bytes
|
|
75
|
+
The message to be signed.
|
|
76
|
+
|
|
77
|
+
Returns
|
|
78
|
+
-------
|
|
79
|
+
bytes
|
|
80
|
+
The signature of the message.
|
|
81
|
+
"""
|
|
82
|
+
signature = private_key.sign(message, ec.ECDSA(hashes.SHA256()))
|
|
83
|
+
return signature
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def verify_signature(
|
|
87
|
+
public_key: ec.EllipticCurvePublicKey, message: bytes, signature: bytes
|
|
88
|
+
) -> bool:
|
|
89
|
+
"""Verify a signature against a message using the provided EC public key.
|
|
90
|
+
|
|
91
|
+
Parameters
|
|
92
|
+
----------
|
|
93
|
+
public_key : ec.EllipticCurvePublicKey
|
|
94
|
+
The EC public key to verify the signature.
|
|
95
|
+
message : bytes
|
|
96
|
+
The original message.
|
|
97
|
+
signature : bytes
|
|
98
|
+
The signature to verify.
|
|
99
|
+
|
|
100
|
+
Returns
|
|
101
|
+
-------
|
|
102
|
+
bool
|
|
103
|
+
True if the signature is valid, False otherwise.
|
|
104
|
+
"""
|
|
105
|
+
try:
|
|
106
|
+
public_key.verify(signature, message, ec.ECDSA(hashes.SHA256()))
|
|
107
|
+
return True
|
|
108
|
+
except InvalidSignature:
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def uses_nist_ec_curve(public_key: ec.EllipticCurvePublicKey) -> bool:
|
|
113
|
+
"""Return True if the provided key uses a NIST EC curve."""
|
|
114
|
+
return isinstance(
|
|
115
|
+
public_key.curve,
|
|
116
|
+
(ec.SECP192R1 | ec.SECP224R1 | ec.SECP256R1 | ec.SECP384R1 | ec.SECP521R1),
|
|
117
|
+
)
|
|
@@ -0,0 +1,175 @@
|
|
|
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
|
+
"""Ed25519-only asymmetric cryptography utilities."""
|
|
16
|
+
|
|
17
|
+
import base64
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from cryptography.exceptions import InvalidSignature
|
|
21
|
+
from cryptography.hazmat.primitives import serialization
|
|
22
|
+
from cryptography.hazmat.primitives.asymmetric import ed25519
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def generate_key_pair() -> tuple[ed25519.Ed25519PrivateKey, ed25519.Ed25519PublicKey]:
|
|
26
|
+
"""Generate an Ed25519 private/public key pair.
|
|
27
|
+
|
|
28
|
+
Returns
|
|
29
|
+
-------
|
|
30
|
+
Tuple[Ed25519PrivateKey, Ed25519PublicKey]
|
|
31
|
+
Private and public key pair.
|
|
32
|
+
"""
|
|
33
|
+
private_key = ed25519.Ed25519PrivateKey.generate()
|
|
34
|
+
return private_key, private_key.public_key()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def private_key_to_bytes(private_key: ed25519.Ed25519PrivateKey) -> bytes:
|
|
38
|
+
"""Serialize an Ed25519 private key to PEM bytes.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
private_key : Ed25519PrivateKey
|
|
43
|
+
The private key to serialize.
|
|
44
|
+
|
|
45
|
+
Returns
|
|
46
|
+
-------
|
|
47
|
+
bytes
|
|
48
|
+
PEM-encoded private key.
|
|
49
|
+
"""
|
|
50
|
+
return private_key.private_bytes(
|
|
51
|
+
encoding=serialization.Encoding.PEM,
|
|
52
|
+
format=serialization.PrivateFormat.PKCS8,
|
|
53
|
+
encryption_algorithm=serialization.NoEncryption(),
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def bytes_to_private_key(private_key_bytes: bytes) -> ed25519.Ed25519PrivateKey:
|
|
58
|
+
"""Deserialize an Ed25519 private key from PEM bytes.
|
|
59
|
+
|
|
60
|
+
Parameters
|
|
61
|
+
----------
|
|
62
|
+
private_key_bytes : bytes
|
|
63
|
+
PEM-encoded private key.
|
|
64
|
+
|
|
65
|
+
Returns
|
|
66
|
+
-------
|
|
67
|
+
Ed25519PrivateKey
|
|
68
|
+
Deserialized private key.
|
|
69
|
+
"""
|
|
70
|
+
return serialization.load_pem_private_key(
|
|
71
|
+
private_key_bytes, password=None
|
|
72
|
+
) # type: ignore[return-value]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def public_key_to_bytes(public_key: ed25519.Ed25519PublicKey) -> bytes:
|
|
76
|
+
"""Serialize an Ed25519 public key to PEM bytes.
|
|
77
|
+
|
|
78
|
+
Parameters
|
|
79
|
+
----------
|
|
80
|
+
public_key : Ed25519PublicKey
|
|
81
|
+
The public key to serialize.
|
|
82
|
+
|
|
83
|
+
Returns
|
|
84
|
+
-------
|
|
85
|
+
bytes
|
|
86
|
+
PEM-encoded public key.
|
|
87
|
+
"""
|
|
88
|
+
return public_key.public_bytes(
|
|
89
|
+
encoding=serialization.Encoding.PEM,
|
|
90
|
+
format=serialization.PublicFormat.SubjectPublicKeyInfo,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def bytes_to_public_key(public_key_bytes: bytes) -> ed25519.Ed25519PublicKey:
|
|
95
|
+
"""Deserialize an Ed25519 public key from PEM bytes.
|
|
96
|
+
|
|
97
|
+
Parameters
|
|
98
|
+
----------
|
|
99
|
+
public_key_bytes : bytes
|
|
100
|
+
PEM-encoded public key.
|
|
101
|
+
|
|
102
|
+
Returns
|
|
103
|
+
-------
|
|
104
|
+
Ed25519PublicKey
|
|
105
|
+
Deserialized public key.
|
|
106
|
+
"""
|
|
107
|
+
return serialization.load_pem_public_key(public_key_bytes) # type: ignore[return-value]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def sign_message(private_key: ed25519.Ed25519PrivateKey, message: bytes) -> bytes:
|
|
111
|
+
"""Sign a message using an Ed25519 private key.
|
|
112
|
+
|
|
113
|
+
Parameters
|
|
114
|
+
----------
|
|
115
|
+
private_key : Ed25519PrivateKey
|
|
116
|
+
The private key used for signing.
|
|
117
|
+
message : bytes
|
|
118
|
+
The message to sign.
|
|
119
|
+
|
|
120
|
+
Returns
|
|
121
|
+
-------
|
|
122
|
+
bytes
|
|
123
|
+
The signature of the message.
|
|
124
|
+
"""
|
|
125
|
+
return private_key.sign(message)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def verify_signature(
|
|
129
|
+
public_key: ed25519.Ed25519PublicKey, message: bytes, signature: bytes
|
|
130
|
+
) -> bool:
|
|
131
|
+
"""Verify a signature using an Ed25519 public key.
|
|
132
|
+
|
|
133
|
+
Parameters
|
|
134
|
+
----------
|
|
135
|
+
public_key : Ed25519PublicKey
|
|
136
|
+
The public key used for verification.
|
|
137
|
+
message : bytes
|
|
138
|
+
The original message.
|
|
139
|
+
signature : bytes
|
|
140
|
+
The signature to verify.
|
|
141
|
+
|
|
142
|
+
Returns
|
|
143
|
+
-------
|
|
144
|
+
bool
|
|
145
|
+
True if the signature is valid, False otherwise.
|
|
146
|
+
"""
|
|
147
|
+
try:
|
|
148
|
+
public_key.verify(signature, message)
|
|
149
|
+
return True
|
|
150
|
+
except InvalidSignature:
|
|
151
|
+
return False
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def create_message_to_sign(fab_digest: bytes, timestamp: int) -> bytes:
|
|
155
|
+
"""Create a canonical message:
|
|
156
|
+
timestamp (8 bytes big-endian) + fab_digest.
|
|
157
|
+
"""
|
|
158
|
+
timestamp_bytes = timestamp.to_bytes(8, byteorder="big")
|
|
159
|
+
return timestamp_bytes + fab_digest
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def decode_base64url(sig: str) -> bytes:
|
|
163
|
+
"""Convert signature to b64 format."""
|
|
164
|
+
# add missing padding (=) to a multiple of 4
|
|
165
|
+
pad = (-len(sig)) % 4
|
|
166
|
+
return base64.urlsafe_b64decode(sig + ("=" * pad))
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def load_private_key(path: Path) -> ed25519.Ed25519PrivateKey:
|
|
170
|
+
"""Load an SSH-format private key (Ed25519) using cryptography."""
|
|
171
|
+
key_bytes = path.read_bytes()
|
|
172
|
+
private_key = serialization.load_ssh_private_key(key_bytes, password=None)
|
|
173
|
+
if not isinstance(private_key, ed25519.Ed25519PrivateKey):
|
|
174
|
+
raise ValueError("Private key is not Ed25519")
|
|
175
|
+
return private_key
|
|
@@ -0,0 +1,159 @@
|
|
|
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
|
+
"""Mixin providing common SQLite connection and initialization logic."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
import re
|
|
19
|
+
import sqlite3
|
|
20
|
+
from abc import ABC
|
|
21
|
+
from collections.abc import Sequence
|
|
22
|
+
from logging import DEBUG, ERROR
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
from flwr.common.logger import log
|
|
26
|
+
|
|
27
|
+
DictOrTuple = tuple[Any, ...] | dict[str, Any]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class SqliteMixin(ABC):
|
|
31
|
+
"""Mixin providing common SQLite connection and initialization logic."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, database_path: str) -> None:
|
|
34
|
+
self.database_path = database_path
|
|
35
|
+
self._conn: sqlite3.Connection | None = None
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def conn(self) -> sqlite3.Connection:
|
|
39
|
+
"""Get the SQLite connection."""
|
|
40
|
+
if self._conn is None:
|
|
41
|
+
raise AttributeError("Database not initialized. Call initialize() first.")
|
|
42
|
+
return self._conn
|
|
43
|
+
|
|
44
|
+
def get_sql_statements(self) -> tuple[str, ...]:
|
|
45
|
+
"""Return SQL statements for this class.
|
|
46
|
+
|
|
47
|
+
Subclasses can override this to provide their SQL CREATE statements.
|
|
48
|
+
The base implementation returns an empty tuple.
|
|
49
|
+
|
|
50
|
+
Returns
|
|
51
|
+
-------
|
|
52
|
+
tuple[str, ...]
|
|
53
|
+
SQL CREATE TABLE/INDEX statements for this class.
|
|
54
|
+
"""
|
|
55
|
+
return ()
|
|
56
|
+
|
|
57
|
+
def initialize(self, log_queries: bool = False) -> list[tuple[str]]:
|
|
58
|
+
"""Connect to the DB, enable FK support, and create tables if needed.
|
|
59
|
+
|
|
60
|
+
This method executes SQL statements returned by `get_sql_statements()`.
|
|
61
|
+
|
|
62
|
+
Parameters
|
|
63
|
+
----------
|
|
64
|
+
log_queries : bool
|
|
65
|
+
Log each query which is executed.
|
|
66
|
+
|
|
67
|
+
Returns
|
|
68
|
+
-------
|
|
69
|
+
list[tuple[str]]
|
|
70
|
+
The list of all tables in the DB.
|
|
71
|
+
|
|
72
|
+
Examples
|
|
73
|
+
--------
|
|
74
|
+
Override `get_sql_statements()` in your subclass:
|
|
75
|
+
|
|
76
|
+
.. code:: python
|
|
77
|
+
|
|
78
|
+
def get_sql_statements(self) -> tuple[str, ...]:
|
|
79
|
+
return (
|
|
80
|
+
SQL_CREATE_TABLE_FOO,
|
|
81
|
+
SQL_CREATE_TABLE_BAR,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
To include parent SQL statements, call super():
|
|
85
|
+
|
|
86
|
+
.. code:: python
|
|
87
|
+
|
|
88
|
+
def get_sql_statements(self) -> tuple[str, ...]:
|
|
89
|
+
return super().get_sql_statements() + (
|
|
90
|
+
SQL_CREATE_TABLE_FOO,
|
|
91
|
+
SQL_CREATE_TABLE_BAR,
|
|
92
|
+
)
|
|
93
|
+
"""
|
|
94
|
+
self._conn = sqlite3.connect(self.database_path)
|
|
95
|
+
# Enable Write-Ahead Logging (WAL) for better concurrency
|
|
96
|
+
self._conn.execute("PRAGMA journal_mode = WAL;")
|
|
97
|
+
self._conn.execute("PRAGMA synchronous = NORMAL;")
|
|
98
|
+
self._conn.execute("PRAGMA foreign_keys = ON;")
|
|
99
|
+
self._conn.execute("PRAGMA cache_size = -64000;") # 64MB cache
|
|
100
|
+
self._conn.execute("PRAGMA temp_store = MEMORY;") # In-memory temp tables
|
|
101
|
+
self._conn.execute("PRAGMA mmap_size = 268435456;") # 256MB memory-mapped I/O
|
|
102
|
+
self._conn.row_factory = dict_factory
|
|
103
|
+
|
|
104
|
+
if log_queries:
|
|
105
|
+
self._conn.set_trace_callback(lambda q: log(DEBUG, q))
|
|
106
|
+
|
|
107
|
+
# Create tables and indexes
|
|
108
|
+
cur = self._conn.cursor()
|
|
109
|
+
for sql in self.get_sql_statements():
|
|
110
|
+
cur.execute(sql)
|
|
111
|
+
res = cur.execute("SELECT name FROM sqlite_schema;")
|
|
112
|
+
return res.fetchall()
|
|
113
|
+
|
|
114
|
+
def query(
|
|
115
|
+
self,
|
|
116
|
+
query: str,
|
|
117
|
+
data: Sequence[DictOrTuple] | DictOrTuple | None = None,
|
|
118
|
+
) -> list[dict[str, Any]]:
|
|
119
|
+
"""Execute a SQL query and return the results as list of dicts."""
|
|
120
|
+
if self._conn is None:
|
|
121
|
+
raise AttributeError("LinkState is not initialized.")
|
|
122
|
+
|
|
123
|
+
if data is None:
|
|
124
|
+
data = []
|
|
125
|
+
|
|
126
|
+
# Clean up whitespace to make the logs nicer
|
|
127
|
+
query = re.sub(r"\s+", " ", query)
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
with self._conn:
|
|
131
|
+
if (
|
|
132
|
+
len(data) > 0
|
|
133
|
+
and isinstance(data, (tuple | list))
|
|
134
|
+
and isinstance(data[0], (tuple | dict))
|
|
135
|
+
):
|
|
136
|
+
rows = self._conn.executemany(query, data)
|
|
137
|
+
else:
|
|
138
|
+
rows = self._conn.execute(query, data)
|
|
139
|
+
|
|
140
|
+
# Extract results before committing to support
|
|
141
|
+
# INSERT/UPDATE ... RETURNING
|
|
142
|
+
# style queries
|
|
143
|
+
result = rows.fetchall()
|
|
144
|
+
except KeyError as exc:
|
|
145
|
+
log(ERROR, {"query": query, "data": data, "exception": exc})
|
|
146
|
+
|
|
147
|
+
return result
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def dict_factory(
|
|
151
|
+
cursor: sqlite3.Cursor,
|
|
152
|
+
row: sqlite3.Row,
|
|
153
|
+
) -> dict[str, Any]:
|
|
154
|
+
"""Turn SQLite results into dicts.
|
|
155
|
+
|
|
156
|
+
Less efficent for retrival of large amounts of data but easier to use.
|
|
157
|
+
"""
|
|
158
|
+
fields = [column[0] for column in cursor.description]
|
|
159
|
+
return dict(zip(fields, row, strict=True))
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
import os
|
|
19
19
|
import subprocess
|
|
20
20
|
from collections.abc import Sequence
|
|
21
|
-
from typing import Optional
|
|
22
21
|
|
|
23
22
|
from .exec_plugin import ExecPlugin
|
|
24
23
|
|
|
@@ -33,7 +32,7 @@ class BaseExecPlugin(ExecPlugin):
|
|
|
33
32
|
command = ""
|
|
34
33
|
appio_api_address_arg = ""
|
|
35
34
|
|
|
36
|
-
def select_run_id(self, candidate_run_ids: Sequence[int]) ->
|
|
35
|
+
def select_run_id(self, candidate_run_ids: Sequence[int]) -> int | None:
|
|
37
36
|
"""Select a run ID to execute from a sequence of candidates."""
|
|
38
37
|
if not candidate_run_ids:
|
|
39
38
|
return None
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
from abc import ABC, abstractmethod
|
|
19
|
-
from collections.abc import Sequence
|
|
20
|
-
from typing import Any
|
|
19
|
+
from collections.abc import Callable, Sequence
|
|
20
|
+
from typing import Any
|
|
21
21
|
|
|
22
22
|
from flwr.common.typing import Run
|
|
23
23
|
|
|
@@ -36,7 +36,7 @@ class ExecPlugin(ABC):
|
|
|
36
36
|
self.get_run = get_run
|
|
37
37
|
|
|
38
38
|
@abstractmethod
|
|
39
|
-
def select_run_id(self, candidate_run_ids: Sequence[int]) ->
|
|
39
|
+
def select_run_id(self, candidate_run_ids: Sequence[int]) -> int | None:
|
|
40
40
|
"""Select a run ID to execute from a sequence of candidates.
|
|
41
41
|
|
|
42
42
|
A candidate run ID is one that has at least one pending message and is
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import time
|
|
19
19
|
from logging import WARN
|
|
20
|
-
from typing import Any
|
|
20
|
+
from typing import Any
|
|
21
21
|
|
|
22
22
|
from flwr.common.config import get_flwr_dir
|
|
23
23
|
from flwr.common.exit import ExitCode, flwr_exit, register_signal_handlers
|
|
@@ -43,14 +43,12 @@ from .plugin import ExecPlugin
|
|
|
43
43
|
|
|
44
44
|
def run_superexec( # pylint: disable=R0913,R0914,R0917
|
|
45
45
|
plugin_class: type[ExecPlugin],
|
|
46
|
-
stub_class:
|
|
47
|
-
type[ClientAppIoStub], type[ServerAppIoStub], type[SimulationIoStub]
|
|
48
|
-
],
|
|
46
|
+
stub_class: type[ClientAppIoStub] | type[ServerAppIoStub] | type[SimulationIoStub],
|
|
49
47
|
appio_api_address: str,
|
|
50
|
-
plugin_config:
|
|
51
|
-
flwr_dir:
|
|
52
|
-
parent_pid:
|
|
53
|
-
health_server_address:
|
|
48
|
+
plugin_config: dict[str, Any] | None = None,
|
|
49
|
+
flwr_dir: str | None = None,
|
|
50
|
+
parent_pid: int | None = None,
|
|
51
|
+
health_server_address: str | None = None,
|
|
54
52
|
) -> None:
|
|
55
53
|
"""Run Flower SuperExec.
|
|
56
54
|
|
|
@@ -158,12 +156,10 @@ def run_with_deprecation_warning( # pylint: disable=R0913, R0917
|
|
|
158
156
|
cmd: str,
|
|
159
157
|
plugin_type: str,
|
|
160
158
|
plugin_class: type[ExecPlugin],
|
|
161
|
-
stub_class:
|
|
162
|
-
type[ClientAppIoStub], type[ServerAppIoStub], type[SimulationIoStub]
|
|
163
|
-
],
|
|
159
|
+
stub_class: type[ClientAppIoStub] | type[ServerAppIoStub] | type[SimulationIoStub],
|
|
164
160
|
appio_api_address: str,
|
|
165
|
-
flwr_dir:
|
|
166
|
-
parent_pid:
|
|
161
|
+
flwr_dir: str | None,
|
|
162
|
+
parent_pid: int | None,
|
|
167
163
|
warn_run_once: bool,
|
|
168
164
|
) -> None:
|
|
169
165
|
"""Log a deprecation warning and run the equivalent `flower-superexec` command.
|
flwr/supercore/utils.py
CHANGED
|
@@ -30,3 +30,23 @@ def mask_string(value: str, head: int = 4, tail: int = 4) -> str:
|
|
|
30
30
|
if len(value) <= head + tail:
|
|
31
31
|
return value
|
|
32
32
|
return f"{value[:head]}...{value[-tail:]}"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def uint64_to_int64(unsigned: int) -> int:
|
|
36
|
+
"""Convert a uint64 integer to a sint64 with the same bit pattern.
|
|
37
|
+
|
|
38
|
+
For values >= 2^63, wraps around by subtracting 2^64.
|
|
39
|
+
"""
|
|
40
|
+
if unsigned >= (1 << 63):
|
|
41
|
+
return unsigned - (1 << 64)
|
|
42
|
+
return unsigned
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def int64_to_uint64(signed: int) -> int:
|
|
46
|
+
"""Convert a sint64 integer to a uint64 with the same bit pattern.
|
|
47
|
+
|
|
48
|
+
For negative values, wraps around by adding 2^64.
|
|
49
|
+
"""
|
|
50
|
+
if signed < 0:
|
|
51
|
+
return signed + (1 << 64)
|
|
52
|
+
return signed
|
|
@@ -16,14 +16,13 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
from abc import ABC, abstractmethod
|
|
19
|
-
from typing import Optional
|
|
20
19
|
|
|
21
20
|
|
|
22
21
|
class ArtifactProvider(ABC):
|
|
23
22
|
"""ArtifactProvider interface for providing artifact download links."""
|
|
24
23
|
|
|
25
24
|
@abstractmethod
|
|
26
|
-
def get_url(self, run_id: int) ->
|
|
25
|
+
def get_url(self, run_id: int) -> str | None:
|
|
27
26
|
"""Return the artifact download link for the given run ID."""
|
|
28
27
|
|
|
29
28
|
@property
|
|
@@ -12,15 +12,15 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
# ==============================================================================
|
|
15
|
-
"""
|
|
15
|
+
"""Account auth plugin for ControlServicer."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
from .auth_plugin import
|
|
19
|
-
from .
|
|
20
|
-
from .auth_plugin import ControlAuthzPlugin as ControlAuthzPlugin
|
|
18
|
+
from .auth_plugin import ControlAuthnPlugin, ControlAuthzPlugin
|
|
19
|
+
from .noop_auth_plugin import NoOpControlAuthnPlugin, NoOpControlAuthzPlugin
|
|
21
20
|
|
|
22
21
|
__all__ = [
|
|
23
|
-
"
|
|
24
|
-
"ControlAuthPlugin",
|
|
22
|
+
"ControlAuthnPlugin",
|
|
25
23
|
"ControlAuthzPlugin",
|
|
24
|
+
"NoOpControlAuthnPlugin",
|
|
25
|
+
"NoOpControlAuthzPlugin",
|
|
26
26
|
]
|
|
@@ -0,0 +1,88 @@
|
|
|
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 plugins."""
|
|
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 (
|
|
23
|
+
AccountAuthCredentials,
|
|
24
|
+
AccountAuthLoginDetails,
|
|
25
|
+
AccountInfo,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ControlAuthnPlugin(ABC):
|
|
30
|
+
"""Abstract Flower Authentication Plugin class for ControlServicer.
|
|
31
|
+
|
|
32
|
+
Parameters
|
|
33
|
+
----------
|
|
34
|
+
account_auth_config_path : Path
|
|
35
|
+
Path to the YAML file containing the authentication configuration.
|
|
36
|
+
verify_tls_cert : bool
|
|
37
|
+
Boolean indicating whether to verify the TLS certificate
|
|
38
|
+
when making requests to the server.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
account_auth_config_path: Path,
|
|
45
|
+
verify_tls_cert: bool,
|
|
46
|
+
):
|
|
47
|
+
"""Abstract constructor."""
|
|
48
|
+
|
|
49
|
+
@abstractmethod
|
|
50
|
+
def get_login_details(self) -> AccountAuthLoginDetails | None:
|
|
51
|
+
"""Get the login details."""
|
|
52
|
+
|
|
53
|
+
@abstractmethod
|
|
54
|
+
def validate_tokens_in_metadata(
|
|
55
|
+
self, metadata: Sequence[tuple[str, str | bytes]]
|
|
56
|
+
) -> tuple[bool, AccountInfo | None]:
|
|
57
|
+
"""Validate authentication tokens in the provided metadata."""
|
|
58
|
+
|
|
59
|
+
@abstractmethod
|
|
60
|
+
def get_auth_tokens(self, device_code: str) -> AccountAuthCredentials | None:
|
|
61
|
+
"""Get authentication tokens."""
|
|
62
|
+
|
|
63
|
+
@abstractmethod
|
|
64
|
+
def refresh_tokens(
|
|
65
|
+
self, metadata: Sequence[tuple[str, str | bytes]]
|
|
66
|
+
) -> tuple[Sequence[tuple[str, str | bytes]] | None, AccountInfo | None]:
|
|
67
|
+
"""Refresh authentication tokens in the provided metadata."""
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class ControlAuthzPlugin(ABC): # pylint: disable=too-few-public-methods
|
|
71
|
+
"""Abstract Flower Authorization Plugin class for ControlServicer.
|
|
72
|
+
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
account_auth_config_path : Path
|
|
76
|
+
Path to the YAML file containing the authorization configuration.
|
|
77
|
+
verify_tls_cert : bool
|
|
78
|
+
Boolean indicating whether to verify the TLS certificate
|
|
79
|
+
when making requests to the server.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
@abstractmethod
|
|
83
|
+
def __init__(self, account_auth_config_path: Path, verify_tls_cert: bool):
|
|
84
|
+
"""Abstract constructor."""
|
|
85
|
+
|
|
86
|
+
@abstractmethod
|
|
87
|
+
def authorize(self, account_info: AccountInfo) -> bool:
|
|
88
|
+
"""Verify account authorization request."""
|