forked from rucio/rucio
/
authentication.py
301 lines (228 loc) · 12.6 KB
/
authentication.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
# Copyright European Organization for Nuclear Research (CERN) since 2012
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import TYPE_CHECKING
from rucio.api import permission
from rucio.common import exception
from rucio.common.types import InternalAccount
from rucio.common.utils import api_update_return_dict
from rucio.core import authentication, identity, oidc
from rucio.db.sqla.constants import IdentityType
from rucio.db.sqla.session import transactional_session
if TYPE_CHECKING:
from sqlalchemy.orm import Session
@transactional_session
def refresh_cli_auth_token(token_string, account, vo='def', *, session: "Session"):
"""
Checks if there is active refresh token and if so returns
either active token with expiration timestamp or requests a new
refresh and returns new access token.
:param token_string: token string
:param account: Rucio account for which token refresh should be considered
:param session: The database session in use.
:return: tuple of (access token, expiration epoch), None otherswise
"""
account = InternalAccount(account, vo=vo)
return oidc.refresh_cli_auth_token(token_string, account, session=session)
@transactional_session
def redirect_auth_oidc(authn_code, fetchtoken=False, *, session: "Session"):
"""
Finds the Authentication URL in the Rucio DB oauth_requests table
and redirects user's browser to this URL.
:param auth_code: Rucio assigned code to redirect
authorization securely to IdP via Rucio Auth server through a browser.
:param fetchtoken: If True, valid token temporarily saved in the oauth_requests table
will be returned. If False, redirection URL is returned.
:param session: The database session in use.
:returns: result of the query (authorization URL or a
token if a user asks with the correct code) or None.
Exception thrown in case of an unexpected crash.
"""
return authentication.redirect_auth_oidc(authn_code, fetchtoken, session=session)
@transactional_session
def get_auth_oidc(account, vo='def', *, session: "Session", **kwargs):
"""
Assembles the authorization request of the Rucio Client tailored to the Rucio user
& Identity Provider. Saves authentication session parameters in the oauth_requests
DB table (for later use-cases). This information is saved for the token lifetime
of a token to allow token exchange and refresh.
Returns authorization URL as a string or a redirection url to
be used in user's browser for authentication.
:param account: Rucio Account identifier as a string.
:param vo: The VO to act on.
:param auth_scope: space separated list of scope names. Scope parameter
defines which user's info the user allows to provide
to the Rucio Client.
:param audience: audience for which tokens are requested ('rucio' is the default)
:param auto: If True, the function will return authorization URL to the Rucio Client
which will log-in with user's IdP credentials automatically.
Also it will instruct the IdP to return an AuthZ code to another Rucio REST
endpoint /oidc_token. If False, the function will return a URL
to be used by the user in the browser in order to authenticate via IdP
(which will then return with AuthZ code to /oidc_code REST endpoint).
:param polling: If True, '_polling' string will be appended to the access_msg
in the DB oauth_requests table to inform the authorization stage
that the Rucio Client is polling the server for a token
(and no fetchcode needs to be returned at the end).
:param refresh_lifetime: specifies how long the OAuth daemon should
be refreshing this token. Default is 96 hours.
:param ip: IP address of the client as a string.
:param session: The database session in use.
:returns: User & Rucio OIDC Client specific Authorization or Redirection URL as a string
OR a redirection url to be used in user's browser for authentication.
"""
# no permission layer for the moment !
account = InternalAccount(account, vo=vo)
return oidc.get_auth_oidc(account, session=session, **kwargs)
@transactional_session
def get_token_oidc(auth_query_string, ip=None, *, session: "Session"):
"""
After Rucio User got redirected to Rucio /auth/oidc_token (or /auth/oidc_code)
REST endpoints with authz code and session state encoded within the URL.
These parameters are used to eventually gets user's info and tokens from IdP.
:param auth_query_string: IdP redirection URL query string (AuthZ code & user session state).
:param ip: IP address of the client as a string.
:param session: The database session in use.
:returns: One of the following tuples: ("fetchcode", <code>); ("token", <token>);
("polling", True); The result depends on the authentication strategy being used
(no auto, auto, polling).
"""
# no permission layer for the moment !
return oidc.get_token_oidc(auth_query_string, ip, session=session)
@transactional_session
def get_auth_token_user_pass(account, username, password, appid, ip=None, vo='def', *, session: "Session"):
"""
Authenticate a Rucio account temporarily via username and password.
The token lifetime is 1 hour.
:param account: Account identifier as a string.
:param username: Username as a string.
:param password: SHA1 hash of the password as a string.
:param appid: The application identifier as a string.
:param ip: IP address of the client as a string.
:param vo: The VO to act on.
:param session: The database session in use.
:returns: A dict with token and expires_at entries.
"""
kwargs = {'account': account, 'username': username, 'password': password}
if not permission.has_permission(issuer=account, vo=vo, action='get_auth_token_user_pass', kwargs=kwargs, session=session):
raise exception.AccessDenied('User with identity %s can not log to account %s' % (username, account))
account = InternalAccount(account, vo=vo)
return authentication.get_auth_token_user_pass(account, username, password, appid, ip, session=session)
@transactional_session
def get_auth_token_gss(account, gsscred, appid, ip=None, vo='def', *, session: "Session"):
"""
Authenticate a Rucio account temporarily via a GSS token.
The tokens lifetime is 1 hour.
:param account: Account identifier as a string.
:param gsscred: GSS principal@REALM as a string.
:param appid: The application identifier as a string.
:param ip: IP address of the client as a string.
:param vo: The VO to act on.
:param session: The database session in use.
:returns: A dict with token and expires_at entries.
"""
kwargs = {'account': account, 'gsscred': gsscred}
if not permission.has_permission(issuer=account, vo=vo, action='get_auth_token_gss', kwargs=kwargs, session=session):
raise exception.AccessDenied('User with identity %s can not log to account %s' % (gsscred, account))
account = InternalAccount(account, vo=vo)
return authentication.get_auth_token_gss(account, gsscred, appid, ip, session=session)
@transactional_session
def get_auth_token_x509(account, dn, appid, ip=None, vo='def', *, session: "Session"):
"""
Authenticate a Rucio account temporarily via an x509 certificate.
The token lifetime is 1 hour.
:param account: Account identifier as a string. If account is none, the default will be used.
:param dn: Client certificate distinguished name string, as extracted by Apache/mod_ssl.
:param appid: The application identifier as a string.
:param ip: IP address of the client as a string.
:param vo: The VO to act on.
:param session: The database session in use.
:returns: A dict with token and expires_at entries.
"""
if account is None:
account = identity.get_default_account(dn, IdentityType.X509).external
kwargs = {'account': account, 'dn': dn}
if not permission.has_permission(issuer=account, vo=vo, action='get_auth_token_x509', kwargs=kwargs, session=session):
raise exception.AccessDenied('User with identity %s can not log to account %s' % (dn, account))
account = InternalAccount(account, vo=vo)
return authentication.get_auth_token_x509(account, dn, appid, ip, session=session)
@transactional_session
def get_auth_token_ssh(account, signature, appid, ip=None, vo='def', *, session: "Session"):
"""
Authenticate a Rucio account temporarily via SSH key exchange.
The token lifetime is 1 hour.
:param account: Account identifier as a string.
:param signature: Response to challenge token signed with SSH private key as a base64 encoded string.
:param appid: The application identifier as a string.
:param ip: IP address of the client as a string.
:param vo: The VO to act on.
:param session: The database session in use.
:returns: A dict with token and expires_at entries.
"""
kwargs = {'account': account, 'signature': signature}
if not permission.has_permission(issuer=account, vo=vo, action='get_auth_token_ssh', kwargs=kwargs, session=session):
raise exception.AccessDenied('User with provided signature can not log to account %s' % account)
account = InternalAccount(account, vo=vo)
return authentication.get_auth_token_ssh(account, signature, appid, ip, session=session)
@transactional_session
def get_ssh_challenge_token(account, appid, ip=None, vo='def', *, session: "Session"):
"""
Get a challenge token for subsequent SSH public key authentication.
The challenge token lifetime is 5 seconds.
:param account: Account identifier as a string.
:param appid: The application identifier as a string.
:param ip: IP address of the client as a string.
:param vo: The VO to act on.
:param session: The database session in use.
:returns: A dict with token and expires_at entries.
"""
kwargs = {'account': account}
if not permission.has_permission(issuer=account, vo=vo, action='get_auth_token_ssh', kwargs=kwargs, session=session):
raise exception.AccessDenied('User can not get challenge token for account %s' % account)
account = InternalAccount(account, vo=vo)
return authentication.get_ssh_challenge_token(account, appid, ip, session=session)
@transactional_session
def get_auth_token_saml(account, saml_nameid, appid, ip=None, vo='def', *, session: "Session"):
"""
Authenticate a Rucio account temporarily via SSO.
The token lifetime is 1 hour.
:param account: Account identifier as a string.
:param saml_nameid: NameId returned in SAML response as a string.
:param appid: The application identifier as a string.
:param ip: IP address of the client as a string.
:param session: The database session in use.
:returns: A dict with token and expires_at entries.
"""
kwargs = {'account': account, 'saml_nameid': saml_nameid}
if not permission.has_permission(issuer=account, vo=vo, action='get_auth_token_saml', kwargs=kwargs, session=session):
raise exception.AccessDenied('User with identity %s can not log to account %s' % (saml_nameid, account))
account = InternalAccount(account, vo=vo)
return authentication.get_auth_token_saml(account, saml_nameid, appid, ip, session=session)
@transactional_session
def validate_auth_token(token, *, session: "Session"):
"""
Validate an authentication token.
:param token: Authentication token as a variable-length string.
:param session: The database session in use.
:returns: dictionary { account: <account name>,
identity: <identity>,
lifetime: <token lifetime>,
audience: <audience>,
authz_scope: <authz_scope>,
vo: <vo> }
"""
auth = authentication.validate_auth_token(token, session=session)
vo = auth['account'].vo
auth = api_update_return_dict(auth, session=session)
auth['vo'] = vo
return auth