Coverage for python / lsst / daf / butler / remote_butler / server / _dependencies.py: 0%
39 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-17 08:48 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-17 08:48 +0000
1# This file is part of daf_butler.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (http://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This software is dual licensed under the GNU General Public License and also
10# under a 3-clause BSD license. Recipients may choose which of these licenses
11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt,
12# respectively. If you choose the GPL option then the following text applies
13# (but note that there is still no warranty even if you opt for BSD instead):
14#
15# This program is free software: you can redistribute it and/or modify
16# it under the terms of the GNU General Public License as published by
17# the Free Software Foundation, either version 3 of the License, or
18# (at your option) any later version.
19#
20# This program is distributed in the hope that it will be useful,
21# but WITHOUT ANY WARRANTY; without even the implied warranty of
22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23# GNU General Public License for more details.
24#
25# You should have received a copy of the GNU General Public License
26# along with this program. If not, see <http://www.gnu.org/licenses/>.
28from typing import Annotated
30from fastapi import Depends, Header, HTTPException
31from safir.dependencies.gafaelfawr import auth_delegated_token_dependency
32from safir.dependencies.logger import logger_dependency as safir_logger_dependency
33from structlog.stdlib import BoundLogger
35from lsst.daf.butler import LabeledButlerFactory
37from ._config import load_config
38from ._factory import Factory
39from ._gafaelfawr import GafaelfawrClient, GafaelfawrGroupAuthorizer
41_butler_factory: LabeledButlerFactory | None = None
42_authorizer: GafaelfawrGroupAuthorizer | None = None
45async def butler_factory_dependency() -> LabeledButlerFactory:
46 """Return a global LabeledButlerFactory instance. This will be used to
47 construct internal DirectButler instances for interacting with the Butler
48 repositories we are serving.
49 """
50 global _butler_factory
51 if _butler_factory is None:
52 config = load_config()
53 repositories = {k: v.config_uri for k, v in config.repositories.items()}
54 _butler_factory = LabeledButlerFactory(repositories)
55 return _butler_factory
58async def authorizer_dependency() -> GafaelfawrGroupAuthorizer:
59 """Instantiate a client for checking group membership via Gafaelfawr."""
60 global _authorizer
61 if _authorizer is None:
62 config = load_config()
63 authorized_groups = {k: v.authorized_groups for k, v in config.repositories.items()}
64 client = GafaelfawrClient(str(config.gafaelfawr_url))
65 _authorizer = GafaelfawrGroupAuthorizer(client, authorized_groups)
67 return _authorizer
70async def user_name_dependency(x_auth_request_user: Annotated[str | None, Header()] = None) -> str | None:
71 """Retrieve the user name from Gafaelfawr authentication headers.
73 Parameters
74 ----------
75 x_auth_request_user : FastAPI header
76 Header provided by FastAPI.
78 Returns
79 -------
80 user_name : `str` | `None`
81 The user name, if Gafaelfawr is available on this environment. `None`
82 if Gafaelfawr is not available.
83 """
84 if x_auth_request_user is None and load_config().gafaelfawr_enabled:
85 raise HTTPException(status_code=403, detail="Required X-Auth-Request-User header was not provided")
87 return x_auth_request_user
90async def repository_authorization_dependency(
91 repository: str,
92 user_name: Annotated[str | None, Depends(user_name_dependency)],
93 user_token: Annotated[str, Depends(auth_delegated_token_dependency)],
94 authorizer: Annotated[GafaelfawrGroupAuthorizer, Depends(authorizer_dependency)],
95) -> None:
96 """Restrict access to specific repositories based on the user's membership
97 in Gafaelfawr groups.
99 Parameters
100 ----------
101 repository : `str`
102 Butler repository that is being accessed.
103 user_name : `str`
104 Name of the user accessing the repository, from Gafaelfawr headers.
105 user_token : `str`
106 Delegated token for the user accessing the repository, from Gafaelfawr
107 headers. Used for retrieving group membership information about the
108 user from Gafaelfawr.
109 authorizer : `GafaelfawrGroupAuthorizer`
110 Authorization client that will be used to verify group membership.
111 """
112 assert user_name is not None, "Gafaelfawr user name header should have been populated."
113 if not await authorizer.is_user_authorized_for_repository(
114 repository=repository, user_name=user_name, user_token=user_token
115 ):
116 raise HTTPException(
117 status_code=403,
118 detail=f"User {user_name} does not have permission to access Butler repository '{repository}'",
119 )
122async def logger_dependency(
123 logger: Annotated[BoundLogger, Depends(safir_logger_dependency)],
124 repository: str,
125 user_name: Annotated[str | None, Depends(user_name_dependency)],
126 x_auth_request_service: Annotated[str | None, Header()] = None,
127) -> BoundLogger:
128 """Return a logger with additional bound context information.
130 Parameters
131 ----------
132 logger : `structlog.stdlib.BoundLogger`
133 Logger provided by Safir.
134 repository : `str`
135 Butler repository that is being accessed.
136 user_name : `str` or `None`
137 Name of the user accessing the repository, from Gafaelfawr headers.
138 x_auth_request_service : `str` or `None`
139 Name of the service being used to access the repository, from
140 Gafaelfawr headers.
141 """
142 return logger.bind(
143 butler_repo=repository, requester={"username": user_name, "service": x_auth_request_service}
144 )
147async def factory_dependency(
148 repository: str,
149 butler_factory: Annotated[LabeledButlerFactory, Depends(butler_factory_dependency)],
150) -> Factory:
151 """Return Factory object for injection into FastAPI.
153 Parameters
154 ----------
155 repository : `str`
156 Label of the repository for lookup from the repository index.
157 butler_factory : `LabeledButlerFactory`
158 Factory for instantiating DirectButlers.
159 """
160 return Factory(butler_factory=butler_factory, repository=repository)
163def reset_dependency_caches() -> None:
164 """Clear caches used by dependencies. Unit tests should call this after
165 changing the configuration, to allow objects to be re-created with the
166 new configuration.
167 """
168 global _butler_factory
169 global _authorizer
171 _butler_factory = None
172 _authorizer = None