flwr-nightly 1.23.0.dev20251001__py3-none-any.whl → 1.23.0.dev20251003__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 (44) hide show
  1. flwr/cli/app.py +13 -0
  2. flwr/cli/auth_plugin/__init__.py +7 -4
  3. flwr/cli/auth_plugin/auth_plugin.py +23 -11
  4. flwr/cli/auth_plugin/noop_auth_plugin.py +58 -0
  5. flwr/cli/auth_plugin/oidc_cli_plugin.py +15 -25
  6. flwr/cli/{cli_user_auth_interceptor.py → cli_account_auth_interceptor.py} +4 -4
  7. flwr/cli/login/login.py +34 -14
  8. flwr/{client/clientapp → cli/supernode}/__init__.py +11 -1
  9. flwr/cli/supernode/create.py +58 -0
  10. flwr/cli/supernode/delete.py +58 -0
  11. flwr/cli/supernode/ls.py +51 -0
  12. flwr/cli/utils.py +36 -22
  13. flwr/client/__init__.py +2 -1
  14. flwr/clientapp/__init__.py +1 -2
  15. flwr/{client/clientapp → clientapp}/utils.py +1 -1
  16. flwr/common/constant.py +14 -8
  17. flwr/common/typing.py +6 -6
  18. flwr/compat/client/app.py +1 -1
  19. flwr/proto/control_pb2.py +48 -35
  20. flwr/proto/control_pb2.pyi +67 -4
  21. flwr/proto/control_pb2_grpc.py +102 -0
  22. flwr/proto/control_pb2_grpc.pyi +39 -0
  23. flwr/proto/node_pb2.py +3 -1
  24. flwr/proto/node_pb2.pyi +33 -0
  25. flwr/server/app.py +34 -16
  26. flwr/server/superlink/fleet/vce/backend/backend.py +1 -1
  27. flwr/server/superlink/fleet/vce/backend/raybackend.py +1 -1
  28. flwr/server/superlink/fleet/vce/vce_api.py +2 -2
  29. flwr/simulation/ray_transport/ray_actor.py +1 -1
  30. flwr/simulation/ray_transport/ray_client_proxy.py +1 -1
  31. flwr/simulation/run_simulation.py +1 -1
  32. flwr/superlink/auth_plugin/__init__.py +5 -2
  33. flwr/superlink/auth_plugin/auth_plugin.py +16 -12
  34. flwr/superlink/auth_plugin/noop_auth_plugin.py +87 -0
  35. flwr/superlink/servicer/control/{control_user_auth_interceptor.py → control_account_auth_interceptor.py} +19 -19
  36. flwr/superlink/servicer/control/control_event_log_interceptor.py +1 -1
  37. flwr/superlink/servicer/control/control_grpc.py +9 -9
  38. flwr/superlink/servicer/control/control_servicer.py +57 -29
  39. flwr/supernode/runtime/run_clientapp.py +2 -2
  40. {flwr_nightly-1.23.0.dev20251001.dist-info → flwr_nightly-1.23.0.dev20251003.dist-info}/METADATA +1 -1
  41. {flwr_nightly-1.23.0.dev20251001.dist-info → flwr_nightly-1.23.0.dev20251003.dist-info}/RECORD +44 -39
  42. /flwr/{client → clientapp}/client_app.py +0 -0
  43. {flwr_nightly-1.23.0.dev20251001.dist-info → flwr_nightly-1.23.0.dev20251003.dist-info}/WHEEL +0 -0
  44. {flwr_nightly-1.23.0.dev20251001.dist-info → flwr_nightly-1.23.0.dev20251003.dist-info}/entry_points.txt +0 -0
@@ -44,6 +44,21 @@ class ControlStub:
44
44
  flwr.proto.control_pb2.PullArtifactsResponse]
45
45
  """Pull artifacts generated during a run (flwr pull)"""
46
46
 
47
+ CreateNodeCli: grpc.UnaryUnaryMultiCallable[
48
+ flwr.proto.control_pb2.CreateNodeCliRequest,
49
+ flwr.proto.control_pb2.CreateNodeCliResponse]
50
+ """Add SuperNode"""
51
+
52
+ DeleteNodeCli: grpc.UnaryUnaryMultiCallable[
53
+ flwr.proto.control_pb2.DeleteNodeCliRequest,
54
+ flwr.proto.control_pb2.DeleteNodeCliResponse]
55
+ """Remove SuperNode"""
56
+
57
+ ListNodesCli: grpc.UnaryUnaryMultiCallable[
58
+ flwr.proto.control_pb2.ListNodesCliRequest,
59
+ flwr.proto.control_pb2.ListNodesCliResponse]
60
+ """List SuperNodes"""
61
+
47
62
 
48
63
  class ControlServicer(metaclass=abc.ABCMeta):
49
64
  @abc.abstractmethod
@@ -102,5 +117,29 @@ class ControlServicer(metaclass=abc.ABCMeta):
102
117
  """Pull artifacts generated during a run (flwr pull)"""
103
118
  pass
104
119
 
120
+ @abc.abstractmethod
121
+ def CreateNodeCli(self,
122
+ request: flwr.proto.control_pb2.CreateNodeCliRequest,
123
+ context: grpc.ServicerContext,
124
+ ) -> flwr.proto.control_pb2.CreateNodeCliResponse:
125
+ """Add SuperNode"""
126
+ pass
127
+
128
+ @abc.abstractmethod
129
+ def DeleteNodeCli(self,
130
+ request: flwr.proto.control_pb2.DeleteNodeCliRequest,
131
+ context: grpc.ServicerContext,
132
+ ) -> flwr.proto.control_pb2.DeleteNodeCliResponse:
133
+ """Remove SuperNode"""
134
+ pass
135
+
136
+ @abc.abstractmethod
137
+ def ListNodesCli(self,
138
+ request: flwr.proto.control_pb2.ListNodesCliRequest,
139
+ context: grpc.ServicerContext,
140
+ ) -> flwr.proto.control_pb2.ListNodesCliResponse:
141
+ """List SuperNodes"""
142
+ pass
143
+
105
144
 
106
145
  def add_ControlServicer_to_server(servicer: ControlServicer, server: grpc.Server) -> None: ...
flwr/proto/node_pb2.py CHANGED
@@ -14,7 +14,7 @@ _sym_db = _symbol_database.Default()
14
14
 
15
15
 
16
16
 
17
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x66lwr/proto/node.proto\x12\nflwr.proto\"\x17\n\x04Node\x12\x0f\n\x07node_id\x18\x01 \x01(\x04\x62\x06proto3')
17
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x66lwr/proto/node.proto\x12\nflwr.proto\"\x17\n\x04Node\x12\x0f\n\x07node_id\x18\x01 \x01(\x04\"\xb6\x01\n\x08NodeInfo\x12\x0f\n\x07node_id\x18\x01 \x01(\x04\x12\x11\n\towner_aid\x18\x02 \x01(\t\x12\x12\n\ncreated_at\x18\x03 \x01(\t\x12\x14\n\x0c\x61\x63tivated_at\x18\x04 \x01(\t\x12\x16\n\x0e\x64\x65\x61\x63tivated_at\x18\x05 \x01(\t\x12\x12\n\ndeleted_at\x18\x06 \x01(\t\x12\x14\n\x0conline_until\x18\x07 \x01(\x02\x12\x1a\n\x12heartbeat_interval\x18\x08 \x01(\x02\x62\x06proto3')
18
18
 
19
19
  _globals = globals()
20
20
  _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -23,4 +23,6 @@ if _descriptor._USE_C_DESCRIPTORS == False:
23
23
  DESCRIPTOR._options = None
24
24
  _globals['_NODE']._serialized_start=37
25
25
  _globals['_NODE']._serialized_end=60
26
+ _globals['_NODEINFO']._serialized_start=63
27
+ _globals['_NODEINFO']._serialized_end=245
26
28
  # @@protoc_insertion_point(module_scope)
flwr/proto/node_pb2.pyi CHANGED
@@ -5,6 +5,7 @@ isort:skip_file
5
5
  import builtins
6
6
  import google.protobuf.descriptor
7
7
  import google.protobuf.message
8
+ import typing
8
9
  import typing_extensions
9
10
 
10
11
  DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
@@ -19,3 +20,35 @@ class Node(google.protobuf.message.Message):
19
20
  ) -> None: ...
20
21
  def ClearField(self, field_name: typing_extensions.Literal["node_id",b"node_id"]) -> None: ...
21
22
  global___Node = Node
23
+
24
+ class NodeInfo(google.protobuf.message.Message):
25
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
26
+ NODE_ID_FIELD_NUMBER: builtins.int
27
+ OWNER_AID_FIELD_NUMBER: builtins.int
28
+ CREATED_AT_FIELD_NUMBER: builtins.int
29
+ ACTIVATED_AT_FIELD_NUMBER: builtins.int
30
+ DEACTIVATED_AT_FIELD_NUMBER: builtins.int
31
+ DELETED_AT_FIELD_NUMBER: builtins.int
32
+ ONLINE_UNTIL_FIELD_NUMBER: builtins.int
33
+ HEARTBEAT_INTERVAL_FIELD_NUMBER: builtins.int
34
+ node_id: builtins.int
35
+ owner_aid: typing.Text
36
+ created_at: typing.Text
37
+ activated_at: typing.Text
38
+ deactivated_at: typing.Text
39
+ deleted_at: typing.Text
40
+ online_until: builtins.float
41
+ heartbeat_interval: builtins.float
42
+ def __init__(self,
43
+ *,
44
+ node_id: builtins.int = ...,
45
+ owner_aid: typing.Text = ...,
46
+ created_at: typing.Text = ...,
47
+ activated_at: typing.Text = ...,
48
+ deactivated_at: typing.Text = ...,
49
+ deleted_at: typing.Text = ...,
50
+ online_until: builtins.float = ...,
51
+ heartbeat_interval: builtins.float = ...,
52
+ ) -> None: ...
53
+ def ClearField(self, field_name: typing_extensions.Literal["activated_at",b"activated_at","created_at",b"created_at","deactivated_at",b"deactivated_at","deleted_at",b"deleted_at","heartbeat_interval",b"heartbeat_interval","node_id",b"node_id","online_until",b"online_until","owner_aid",b"owner_aid"]) -> None: ...
54
+ global___NodeInfo = NodeInfo
flwr/server/app.py CHANGED
@@ -38,7 +38,7 @@ from flwr.common.address import parse_address
38
38
  from flwr.common.args import try_obtain_server_certificates
39
39
  from flwr.common.config import get_flwr_dir
40
40
  from flwr.common.constant import (
41
- AUTH_TYPE_YAML_KEY,
41
+ AUTHN_TYPE_YAML_KEY,
42
42
  AUTHZ_TYPE_YAML_KEY,
43
43
  CLIENT_OCTET,
44
44
  CONTROL_API_DEFAULT_SERVER_ADDRESS,
@@ -71,7 +71,7 @@ from flwr.supercore.ffs import FfsFactory
71
71
  from flwr.supercore.grpc_health import add_args_health, run_health_server_grpc_no_tls
72
72
  from flwr.supercore.object_store import ObjectStoreFactory
73
73
  from flwr.superlink.artifact_provider import ArtifactProvider
74
- from flwr.superlink.auth_plugin import ControlAuthPlugin, ControlAuthzPlugin
74
+ from flwr.superlink.auth_plugin import ControlAuthnPlugin, ControlAuthzPlugin
75
75
  from flwr.superlink.servicer.control import run_control_api_grpc
76
76
 
77
77
  from .superlink.fleet.grpc_adapter.grpc_adapter_servicer import GrpcAdapterServicer
@@ -83,13 +83,13 @@ from .superlink.simulation.simulationio_grpc import run_simulationio_api_grpc
83
83
 
84
84
  DATABASE = ":flwr-in-memory-state:"
85
85
  BASE_DIR = get_flwr_dir() / "superlink" / "ffs"
86
- P = TypeVar("P", ControlAuthPlugin, ControlAuthzPlugin)
86
+ P = TypeVar("P", ControlAuthnPlugin, ControlAuthzPlugin)
87
87
 
88
88
 
89
89
  try:
90
90
  from flwr.ee import (
91
91
  add_ee_args_superlink,
92
- get_control_auth_plugins,
92
+ get_control_authn_plugins,
93
93
  get_control_authz_plugins,
94
94
  get_control_event_log_writer_plugins,
95
95
  get_ee_artifact_provider,
@@ -101,7 +101,7 @@ except ImportError:
101
101
  def add_ee_args_superlink(parser: argparse.ArgumentParser) -> None:
102
102
  """Add EE-specific arguments to the parser."""
103
103
 
104
- def get_control_auth_plugins() -> dict[str, type[ControlAuthPlugin]]:
104
+ def get_control_authn_plugins() -> dict[str, type[ControlAuthnPlugin]]:
105
105
  """Return all Control API authentication plugins."""
106
106
  raise NotImplementedError("No authentication plugins are currently supported.")
107
107
 
@@ -189,16 +189,23 @@ def run_superlink() -> None:
189
189
  # Obtain certificates
190
190
  certificates = try_obtain_server_certificates(args)
191
191
 
192
- # Disable the user auth TLS check if args.disable_oidc_tls_cert_verification is
192
+ # Disable the account auth TLS check if args.disable_oidc_tls_cert_verification is
193
193
  # provided
194
194
  verify_tls_cert = not getattr(args, "disable_oidc_tls_cert_verification", None)
195
195
 
196
- auth_plugin: Optional[ControlAuthPlugin] = None
196
+ authn_plugin: Optional[ControlAuthnPlugin] = None
197
197
  authz_plugin: Optional[ControlAuthzPlugin] = None
198
198
  event_log_plugin: Optional[EventLogWriterPlugin] = None
199
- # Load the auth plugin if the args.user_auth_config is provided
199
+ # Load the auth plugin if the args.account_auth_config is provided
200
200
  if cfg_path := getattr(args, "user_auth_config", None):
201
- auth_plugin, authz_plugin = _try_obtain_control_auth_plugins(
201
+ log(
202
+ WARN,
203
+ "The `--user-auth-config` flag is deprecated and will be removed in a "
204
+ "future release. Please use `--account-auth-config` instead.",
205
+ )
206
+ args.account_auth_config = cfg_path
207
+ if cfg_path := getattr(args, "account_auth_config", None):
208
+ authn_plugin, authz_plugin = _try_obtain_control_auth_plugins(
202
209
  Path(cfg_path), verify_tls_cert
203
210
  )
204
211
  # Enable event logging if the args.enable_event_log is True
@@ -229,7 +236,7 @@ def run_superlink() -> None:
229
236
  objectstore_factory=objectstore_factory,
230
237
  certificates=certificates,
231
238
  is_simulation=is_simulation,
232
- auth_plugin=auth_plugin,
239
+ authn_plugin=authn_plugin,
233
240
  authz_plugin=authz_plugin,
234
241
  event_log_plugin=event_log_plugin,
235
242
  artifact_provider=artifact_provider,
@@ -444,7 +451,7 @@ def _try_load_public_keys_node_authentication(
444
451
 
445
452
  def _try_obtain_control_auth_plugins(
446
453
  config_path: Path, verify_tls_cert: bool
447
- ) -> tuple[ControlAuthPlugin, ControlAuthzPlugin]:
454
+ ) -> tuple[ControlAuthnPlugin, ControlAuthzPlugin]:
448
455
  """Obtain Control API authentication and authorization plugins."""
449
456
  # Load YAML file
450
457
  with config_path.open("r", encoding="utf-8") as file:
@@ -459,7 +466,7 @@ def _try_obtain_control_auth_plugins(
459
466
  plugins: dict[str, type[P]] = loader()
460
467
  plugin_cls: type[P] = plugins[auth_plugin_name]
461
468
  return plugin_cls(
462
- user_auth_config_path=config_path, verify_tls_cert=verify_tls_cert
469
+ account_auth_config_path=config_path, verify_tls_cert=verify_tls_cert
463
470
  )
464
471
  except KeyError:
465
472
  if auth_plugin_name:
@@ -471,11 +478,22 @@ def _try_obtain_control_auth_plugins(
471
478
  except NotImplementedError:
472
479
  sys.exit(f"No {section} plugins are currently supported.")
473
480
 
481
+ # Warn deprecated authn_type key
482
+ if "authn_type" in config["authentication"]:
483
+ log(
484
+ WARN,
485
+ "The `authn_type` key in the authentication configuration is deprecated. "
486
+ "Use `%s` instead.",
487
+ AUTHN_TYPE_YAML_KEY,
488
+ )
489
+ authn_type = config["authentication"].pop("authn_type")
490
+ config["authentication"][AUTHN_TYPE_YAML_KEY] = authn_type
491
+
474
492
  # Load authentication plugin
475
- auth_plugin = _load_plugin(
493
+ authn_plugin = _load_plugin(
476
494
  section="authentication",
477
- yaml_key=AUTH_TYPE_YAML_KEY,
478
- loader=get_control_auth_plugins,
495
+ yaml_key=AUTHN_TYPE_YAML_KEY,
496
+ loader=get_control_authn_plugins,
479
497
  )
480
498
 
481
499
  # Load authorization plugin
@@ -485,7 +503,7 @@ def _try_obtain_control_auth_plugins(
485
503
  loader=get_control_authz_plugins,
486
504
  )
487
505
 
488
- return auth_plugin, authz_plugin
506
+ return authn_plugin, authz_plugin
489
507
 
490
508
 
491
509
  def _try_obtain_control_event_log_writer_plugin() -> Optional[EventLogWriterPlugin]:
@@ -18,7 +18,7 @@
18
18
  from abc import ABC, abstractmethod
19
19
  from typing import Callable
20
20
 
21
- from flwr.client.client_app import ClientApp
21
+ from flwr.clientapp.client_app import ClientApp
22
22
  from flwr.common.context import Context
23
23
  from flwr.common.message import Message
24
24
  from flwr.common.typing import ConfigRecordValues
@@ -21,7 +21,7 @@ from typing import Callable, Optional, Union
21
21
 
22
22
  import ray
23
23
 
24
- from flwr.client.client_app import ClientApp
24
+ from flwr.clientapp.client_app import ClientApp
25
25
  from flwr.common.constant import PARTITION_ID_KEY
26
26
  from flwr.common.context import Context
27
27
  from flwr.common.logger import log
@@ -27,9 +27,9 @@ from typing import Callable, Optional
27
27
  from uuid import uuid4
28
28
 
29
29
  from flwr.app.error import Error
30
- from flwr.client.client_app import ClientApp, ClientAppException, LoadClientAppError
31
- from flwr.client.clientapp.utils import get_load_client_app_fn
32
30
  from flwr.client.run_info_store import DeprecatedRunInfoStore
31
+ from flwr.clientapp.client_app import ClientApp, ClientAppException, LoadClientAppError
32
+ from flwr.clientapp.utils import get_load_client_app_fn
33
33
  from flwr.common import Message
34
34
  from flwr.common.constant import (
35
35
  HEARTBEAT_MAX_INTERVAL,
@@ -24,7 +24,7 @@ import ray
24
24
  from ray import ObjectRef
25
25
  from ray.util.actor_pool import ActorPool
26
26
 
27
- from flwr.client.client_app import ClientApp, ClientAppException, LoadClientAppError
27
+ from flwr.clientapp.client_app import ClientApp, ClientAppException, LoadClientAppError
28
28
  from flwr.common import Context, Message
29
29
  from flwr.common.logger import log
30
30
 
@@ -21,8 +21,8 @@ from typing import Optional
21
21
 
22
22
  from flwr import common
23
23
  from flwr.client import ClientFnExt
24
- from flwr.client.client_app import ClientApp
25
24
  from flwr.client.run_info_store import DeprecatedRunInfoStore
25
+ from flwr.clientapp.client_app import ClientApp
26
26
  from flwr.common import DEFAULT_TTL, Message, Metadata, RecordDict, now
27
27
  from flwr.common.constant import (
28
28
  NUM_PARTITIONS_KEY,
@@ -30,7 +30,7 @@ from typing import Any, Optional
30
30
 
31
31
  from flwr.cli.config_utils import load_and_validate
32
32
  from flwr.cli.utils import get_sha256_hash
33
- from flwr.client import ClientApp
33
+ from flwr.clientapp import ClientApp
34
34
  from flwr.common import Context, EventType, RecordDict, event, log, now
35
35
  from flwr.common.config import get_fused_config_from_dir, parse_config_args
36
36
  from flwr.common.constant import RUN_ID_NUM_BYTES, Status
@@ -15,9 +15,12 @@
15
15
  """Account auth plugin for ControlServicer."""
16
16
 
17
17
 
18
- from .auth_plugin import ControlAuthPlugin, ControlAuthzPlugin
18
+ from .auth_plugin import ControlAuthnPlugin, ControlAuthzPlugin
19
+ from .noop_auth_plugin import NoOpControlAuthnPlugin, NoOpControlAuthzPlugin
19
20
 
20
21
  __all__ = [
21
- "ControlAuthPlugin",
22
+ "ControlAuthnPlugin",
22
23
  "ControlAuthzPlugin",
24
+ "NoOpControlAuthnPlugin",
25
+ "NoOpControlAuthzPlugin",
23
26
  ]
@@ -12,7 +12,7 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
  # ==============================================================================
15
- """Abstract classes for Flower User Auth Plugin."""
15
+ """Abstract classes for Flower account auth plugins."""
16
16
 
17
17
 
18
18
  from abc import ABC, abstractmethod
@@ -20,15 +20,19 @@ from collections.abc import Sequence
20
20
  from pathlib import Path
21
21
  from typing import Optional, Union
22
22
 
23
- from flwr.common.typing import AccountInfo, UserAuthCredentials, UserAuthLoginDetails
23
+ from flwr.common.typing import (
24
+ AccountAuthCredentials,
25
+ AccountAuthLoginDetails,
26
+ AccountInfo,
27
+ )
24
28
 
25
29
 
26
- class ControlAuthPlugin(ABC):
27
- """Abstract Flower Auth Plugin class for ControlServicer.
30
+ class ControlAuthnPlugin(ABC):
31
+ """Abstract Flower Authentication Plugin class for ControlServicer.
28
32
 
29
33
  Parameters
30
34
  ----------
31
- user_auth_config_path : Path
35
+ account_auth_config_path : Path
32
36
  Path to the YAML file containing the authentication configuration.
33
37
  verify_tls_cert : bool
34
38
  Boolean indicating whether to verify the TLS certificate
@@ -38,13 +42,13 @@ class ControlAuthPlugin(ABC):
38
42
  @abstractmethod
39
43
  def __init__(
40
44
  self,
41
- user_auth_config_path: Path,
45
+ account_auth_config_path: Path,
42
46
  verify_tls_cert: bool,
43
47
  ):
44
48
  """Abstract constructor."""
45
49
 
46
50
  @abstractmethod
47
- def get_login_details(self) -> Optional[UserAuthLoginDetails]:
51
+ def get_login_details(self) -> Optional[AccountAuthLoginDetails]:
48
52
  """Get the login details."""
49
53
 
50
54
  @abstractmethod
@@ -54,7 +58,7 @@ class ControlAuthPlugin(ABC):
54
58
  """Validate authentication tokens in the provided metadata."""
55
59
 
56
60
  @abstractmethod
57
- def get_auth_tokens(self, device_code: str) -> Optional[UserAuthCredentials]:
61
+ def get_auth_tokens(self, device_code: str) -> Optional[AccountAuthCredentials]:
58
62
  """Get authentication tokens."""
59
63
 
60
64
  @abstractmethod
@@ -71,7 +75,7 @@ class ControlAuthzPlugin(ABC): # pylint: disable=too-few-public-methods
71
75
 
72
76
  Parameters
73
77
  ----------
74
- user_auth_config_path : Path
78
+ account_auth_config_path : Path
75
79
  Path to the YAML file containing the authorization configuration.
76
80
  verify_tls_cert : bool
77
81
  Boolean indicating whether to verify the TLS certificate
@@ -79,9 +83,9 @@ class ControlAuthzPlugin(ABC): # pylint: disable=too-few-public-methods
79
83
  """
80
84
 
81
85
  @abstractmethod
82
- def __init__(self, user_auth_config_path: Path, verify_tls_cert: bool):
86
+ def __init__(self, account_auth_config_path: Path, verify_tls_cert: bool):
83
87
  """Abstract constructor."""
84
88
 
85
89
  @abstractmethod
86
- def verify_user_authorization(self, account_info: AccountInfo) -> bool:
87
- """Verify user authorization request."""
90
+ def authorize(self, account_info: AccountInfo) -> bool:
91
+ """Verify account authorization request."""
@@ -0,0 +1,87 @@
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 Servicer-side account authentication and
16
+ authorization plugins."""
17
+
18
+
19
+ from collections.abc import Sequence
20
+ from pathlib import Path
21
+ from typing import Optional, Union
22
+
23
+ from flwr.common.constant import NOOP_ACCOUNT_NAME, NOOP_FLWR_AID, AuthnType
24
+ from flwr.common.typing import (
25
+ AccountAuthCredentials,
26
+ AccountAuthLoginDetails,
27
+ AccountInfo,
28
+ )
29
+
30
+ from .auth_plugin import ControlAuthnPlugin, ControlAuthzPlugin
31
+
32
+ NOOP_ACCOUNT_INFO = AccountInfo(
33
+ flwr_aid=NOOP_FLWR_AID,
34
+ account_name=NOOP_ACCOUNT_NAME,
35
+ )
36
+
37
+
38
+ class NoOpControlAuthnPlugin(ControlAuthnPlugin):
39
+ """No-operation implementation of ControlAuthnPlugin."""
40
+
41
+ def __init__(
42
+ self,
43
+ account_auth_config_path: Path,
44
+ verify_tls_cert: bool,
45
+ ):
46
+ pass
47
+
48
+ def get_login_details(self) -> Optional[AccountAuthLoginDetails]:
49
+ """Get the login details."""
50
+ # This allows the `flwr login` command to load the NoOp plugin accordingly,
51
+ # which then raises a LoginError when attempting to login.
52
+ return AccountAuthLoginDetails(
53
+ authn_type=AuthnType.NOOP, # No operation authn type
54
+ device_code="",
55
+ verification_uri_complete="",
56
+ expires_in=0,
57
+ interval=0,
58
+ )
59
+
60
+ def validate_tokens_in_metadata(
61
+ self, metadata: Sequence[tuple[str, Union[str, bytes]]]
62
+ ) -> tuple[bool, Optional[AccountInfo]]:
63
+ """Return valid for no-op plugin."""
64
+ return True, NOOP_ACCOUNT_INFO
65
+
66
+ def get_auth_tokens(self, device_code: str) -> Optional[AccountAuthCredentials]:
67
+ """Get authentication tokens."""
68
+ raise RuntimeError("NoOp plugin does not support getting auth tokens.")
69
+
70
+ def refresh_tokens(
71
+ self, metadata: Sequence[tuple[str, Union[str, bytes]]]
72
+ ) -> tuple[
73
+ Optional[Sequence[tuple[str, Union[str, bytes]]]], Optional[AccountInfo]
74
+ ]:
75
+ """Refresh authentication tokens in the provided metadata."""
76
+ return metadata, NOOP_ACCOUNT_INFO
77
+
78
+
79
+ class NoOpControlAuthzPlugin(ControlAuthzPlugin):
80
+ """No-operation implementation of ControlAuthzPlugin."""
81
+
82
+ def __init__(self, account_auth_config_path: Path, verify_tls_cert: bool):
83
+ pass
84
+
85
+ def authorize(self, account_info: AccountInfo) -> bool:
86
+ """Return True for no-op plugin."""
87
+ return True
@@ -31,7 +31,7 @@ from flwr.proto.control_pb2 import ( # pylint: disable=E0611
31
31
  StreamLogsRequest,
32
32
  StreamLogsResponse,
33
33
  )
34
- from flwr.superlink.auth_plugin import ControlAuthPlugin, ControlAuthzPlugin
34
+ from flwr.superlink.auth_plugin import ControlAuthnPlugin, ControlAuthzPlugin
35
35
 
36
36
  Request = Union[
37
37
  StartRunRequest,
@@ -50,15 +50,15 @@ shared_account_info: contextvars.ContextVar[AccountInfo] = contextvars.ContextVa
50
50
  )
51
51
 
52
52
 
53
- class ControlUserAuthInterceptor(grpc.ServerInterceptor): # type: ignore
54
- """Control API interceptor for user authentication."""
53
+ class ControlAccountAuthInterceptor(grpc.ServerInterceptor): # type: ignore
54
+ """Control API interceptor for account authentication."""
55
55
 
56
56
  def __init__(
57
57
  self,
58
- auth_plugin: ControlAuthPlugin,
58
+ authn_plugin: ControlAuthnPlugin,
59
59
  authz_plugin: ControlAuthzPlugin,
60
60
  ):
61
- self.auth_plugin = auth_plugin
61
+ self.authn_plugin = authn_plugin
62
62
  self.authz_plugin = authz_plugin
63
63
 
64
64
  def intercept_service(
@@ -96,45 +96,45 @@ class ControlUserAuthInterceptor(grpc.ServerInterceptor): # type: ignore
96
96
  if isinstance(request, (GetLoginDetailsRequest, GetAuthTokensRequest)):
97
97
  return call(request, context) # type: ignore
98
98
 
99
- # For other requests, check if the user is authenticated
100
- valid_tokens, account_info = self.auth_plugin.validate_tokens_in_metadata(
99
+ # For other requests, check if the account is authenticated
100
+ valid_tokens, account_info = self.authn_plugin.validate_tokens_in_metadata(
101
101
  metadata
102
102
  )
103
103
  if valid_tokens:
104
104
  if account_info is None:
105
105
  context.abort(
106
106
  grpc.StatusCode.UNAUTHENTICATED,
107
- "Tokens validated, but user info not found",
107
+ "Tokens validated, but account info not found",
108
108
  )
109
109
  raise grpc.RpcError()
110
- # Store user info in contextvars for authenticated users
110
+ # Store account info in contextvars for authenticated accounts
111
111
  shared_account_info.set(account_info)
112
- # Check if the user is authorized
113
- if not self.authz_plugin.verify_user_authorization(account_info):
112
+ # Check if the account is authorized
113
+ if not self.authz_plugin.authorize(account_info):
114
114
  context.abort(
115
115
  grpc.StatusCode.PERMISSION_DENIED,
116
- "❗️ User not authorized. "
116
+ "❗️ Account not authorized. "
117
117
  "Please contact the SuperLink administrator.",
118
118
  )
119
119
  raise grpc.RpcError()
120
120
  return call(request, context) # type: ignore
121
121
 
122
- # If the user is not authenticated, refresh tokens
123
- tokens, account_info = self.auth_plugin.refresh_tokens(metadata)
122
+ # If the account is not authenticated, refresh tokens
123
+ tokens, account_info = self.authn_plugin.refresh_tokens(metadata)
124
124
  if tokens is not None:
125
125
  if account_info is None:
126
126
  context.abort(
127
127
  grpc.StatusCode.UNAUTHENTICATED,
128
- "Tokens refreshed, but user info not found",
128
+ "Tokens refreshed, but account info not found",
129
129
  )
130
130
  raise grpc.RpcError()
131
- # Store user info in contextvars for authenticated users
131
+ # Store account info in contextvars for authenticated accounts
132
132
  shared_account_info.set(account_info)
133
- # Check if the user is authorized
134
- if not self.authz_plugin.verify_user_authorization(account_info):
133
+ # Check if the account is authorized
134
+ if not self.authz_plugin.authorize(account_info):
135
135
  context.abort(
136
136
  grpc.StatusCode.PERMISSION_DENIED,
137
- "❗️ User not authorized. "
137
+ "❗️ Account not authorized. "
138
138
  "Please contact the SuperLink administrator.",
139
139
  )
140
140
  raise grpc.RpcError()
@@ -24,7 +24,7 @@ from google.protobuf.message import Message as GrpcMessage
24
24
  from flwr.common.event_log_plugin.event_log_plugin import EventLogWriterPlugin
25
25
  from flwr.common.typing import LogEntry
26
26
 
27
- from .control_user_auth_interceptor import shared_account_info
27
+ from .control_account_auth_interceptor import shared_account_info
28
28
 
29
29
 
30
30
  class ControlEventLogInterceptor(grpc.ServerInterceptor): # type: ignore
@@ -31,12 +31,12 @@ from flwr.supercore.ffs import FfsFactory
31
31
  from flwr.supercore.license_plugin import LicensePlugin
32
32
  from flwr.supercore.object_store import ObjectStoreFactory
33
33
  from flwr.superlink.artifact_provider import ArtifactProvider
34
- from flwr.superlink.auth_plugin import ControlAuthPlugin, ControlAuthzPlugin
34
+ from flwr.superlink.auth_plugin import ControlAuthnPlugin, ControlAuthzPlugin
35
35
 
36
+ from .control_account_auth_interceptor import ControlAccountAuthInterceptor
36
37
  from .control_event_log_interceptor import ControlEventLogInterceptor
37
38
  from .control_license_interceptor import ControlLicenseInterceptor
38
39
  from .control_servicer import ControlServicer
39
- from .control_user_auth_interceptor import ControlUserAuthInterceptor
40
40
 
41
41
  try:
42
42
  from flwr.ee import get_license_plugin
@@ -54,7 +54,7 @@ def run_control_api_grpc(
54
54
  objectstore_factory: ObjectStoreFactory,
55
55
  certificates: Optional[tuple[bytes, bytes, bytes]],
56
56
  is_simulation: bool,
57
- auth_plugin: Optional[ControlAuthPlugin] = None,
57
+ authn_plugin: Optional[ControlAuthnPlugin] = None,
58
58
  authz_plugin: Optional[ControlAuthzPlugin] = None,
59
59
  event_log_plugin: Optional[EventLogWriterPlugin] = None,
60
60
  artifact_provider: Optional[ArtifactProvider] = None,
@@ -69,15 +69,15 @@ def run_control_api_grpc(
69
69
  ffs_factory=ffs_factory,
70
70
  objectstore_factory=objectstore_factory,
71
71
  is_simulation=is_simulation,
72
- auth_plugin=auth_plugin,
72
+ authn_plugin=authn_plugin,
73
73
  artifact_provider=artifact_provider,
74
74
  )
75
75
  interceptors: list[grpc.ServerInterceptor] = []
76
76
  if license_plugin is not None:
77
77
  interceptors.append(ControlLicenseInterceptor(license_plugin))
78
- if auth_plugin is not None and authz_plugin is not None:
79
- interceptors.append(ControlUserAuthInterceptor(auth_plugin, authz_plugin))
80
- # Event log interceptor must be added after user auth interceptor
78
+ if authn_plugin is not None and authz_plugin is not None:
79
+ interceptors.append(ControlAccountAuthInterceptor(authn_plugin, authz_plugin))
80
+ # Event log interceptor must be added after account auth interceptor
81
81
  if event_log_plugin is not None:
82
82
  interceptors.append(ControlEventLogInterceptor(event_log_plugin))
83
83
  log(INFO, "Flower event logging enabled")
@@ -90,12 +90,12 @@ def run_control_api_grpc(
90
90
  interceptors=interceptors or None,
91
91
  )
92
92
 
93
- if auth_plugin is None:
93
+ if authn_plugin is None:
94
94
  log(INFO, "Flower Deployment Runtime: Starting Control API on %s", address)
95
95
  else:
96
96
  log(
97
97
  INFO,
98
- "Flower Deployment Runtime: Starting Control API with user "
98
+ "Flower Deployment Runtime: Starting Control API with account "
99
99
  "authentication on %s",
100
100
  address,
101
101
  )