Skip to content

Commit b1cf785

Browse files
authored
Merge branch 'integration' into 8.0_fixOAuth-4
2 parents b6ab495 + 9bc3d76 commit b1cf785

File tree

3 files changed

+107
-108
lines changed

3 files changed

+107
-108
lines changed

src/DIRAC/Core/Tornado/Server/private/BaseRequestHandler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -616,7 +616,7 @@ def __prepare(self):
616616
# If an error occur when reading certificates we close connection
617617
# It can be strange but the RFC, for HTTP, say's that when error happend
618618
# before authentication we return 401 UNAUTHORIZED instead of 403 FORBIDDEN
619-
sLog.debug(str(e))
619+
sLog.exception(e)
620620
sLog.error("Error gathering credentials ", "%s; path %s" % (self.getRemoteAddress(), self.request.path))
621621
raise HTTPError(HTTPStatus.UNAUTHORIZED, str(e))
622622

src/DIRAC/FrameworkSystem/private/authorization/AuthServer.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
wrapIDAsDN,
2222
getDNForUsername,
2323
getIdPForGroup,
24+
getGroupOption,
2425
)
2526
from DIRAC.FrameworkSystem.Client.ProxyManagerClient import ProxyManagerClient
2627
from DIRAC.FrameworkSystem.Client.TokenManagerClient import TokenManagerClient
@@ -156,10 +157,15 @@ def generateProxyOrToken(
156157
# Try every DN to generate a proxy
157158
for dn in userDNs:
158159
sLog.debug("Try to get proxy for %s" % dn)
160+
params = {}
159161
if lifetime:
160-
result = self.proxyCli.downloadProxy(dn, group, requiredTimeLeft=int(lifetime))
162+
params["requiredTimeLeft"] = int(lifetime)
163+
# if the configuration describes adding a VOMS extension, we will do so
164+
if getGroupOption(group, "AutoAddVOMS", False):
165+
result = self.proxyCli.downloadVOMSProxy(dn, group, **params)
161166
else:
162-
result = self.proxyCli.downloadProxy(dn, group)
167+
# otherwise we will return the usual proxy
168+
result = self.proxyCli.downloadProxy(dn, group, **params)
163169
if not result["OK"]:
164170
err.append(result["Message"])
165171
else:

src/DIRAC/FrameworkSystem/scripts/dirac_login.py

Lines changed: 98 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
#!/usr/bin/env python
22
"""
33
With this command you can log in to DIRAC.
4-
54
There are two options:
6-
75
- using a user certificate, creating a proxy.
86
- go through DIRAC Authorization Server by selecting your Identity Provider.
97
10-
118
Example:
129
# Login with default group
1310
$ dirac-login
@@ -19,11 +16,12 @@
1916
import os
2017
import sys
2118
import copy
22-
from prompt_toolkit import prompt
19+
from prompt_toolkit import prompt, print_formatted_text as print, HTML
2320

2421
import DIRAC
2522
from DIRAC import gConfig, gLogger, S_OK, S_ERROR
26-
from DIRAC.Core.Security import Locations
23+
from DIRAC.Core.Security.Locations import getDefaultProxyLocation, getCertificateAndKeyLocation
24+
from DIRAC.Core.Security.VOMS import VOMS
2725
from DIRAC.Core.Security.ProxyFile import writeToProxyFile
2826
from DIRAC.Core.Security.ProxyInfo import getProxyInfo, formatProxyInfoAsString
2927
from DIRAC.Core.Security.X509Chain import X509Chain # pylint: disable=import-error
@@ -36,6 +34,17 @@
3634
readTokenFromFile,
3735
getTokenFileLocation,
3836
)
37+
from DIRAC.ConfigurationSystem.Client.Helpers.Registry import (
38+
getGroupOption,
39+
getVOMSAttributeForGroup,
40+
getVOMSVOForGroup,
41+
findDefaultGroupForDN,
42+
)
43+
44+
# This value shows what authorization way will be by default
45+
DEFAULT_AUTH_WAY = "certificate" # possible values are "certificate", "diracas"
46+
# This value shows what response will be by default
47+
DEFAULT_RESPONSE = "proxy" # possible values are "proxy", "token"
3948

4049

4150
class Params:
@@ -54,109 +63,76 @@ def __init__(self):
5463
self.issuer = None
5564
self.certLoc = None
5665
self.keyLoc = None
57-
self.result = "proxy"
58-
self.authWith = "certificate"
66+
self.response = DEFAULT_RESPONSE
67+
self.authWith = DEFAULT_AUTH_WAY
5968
self.enableCS = True
6069

61-
def disableCS(self, _arg) -> dict:
62-
"""Set issuer
63-
64-
:param arg: issuer
65-
"""
70+
def disableCS(self, _) -> dict:
71+
"""Disable CS"""
6672
self.enableCS = False
6773
return S_OK()
6874

69-
def setIssuer(self, arg: str) -> dict:
70-
"""Set issuer
71-
72-
:param arg: issuer
73-
"""
75+
def setIssuer(self, issuer: str) -> dict:
76+
"""Set DIRAC Authorization Server issuer"""
7477
self.useDIRACAS(None)
75-
self.issuer = arg
78+
self.issuer = issuer
7679
return S_OK()
7780

78-
def useDIRACAS(self, _arg) -> dict:
79-
"""Use DIRAC AS
80-
81-
:param _arg: unuse
82-
"""
81+
def useDIRACAS(self, _) -> dict:
82+
"""Use DIRAC AS"""
8383
self.authWith = "diracas"
8484
return S_OK()
8585

86-
def useCertificate(self, _arg) -> dict:
87-
"""Use certificate
88-
89-
:param _arg: unuse
90-
"""
86+
def useCertificate(self, _) -> dict:
87+
"""Use certificate"""
9188
os.environ["DIRAC_USE_ACCESS_TOKEN"] = "false"
9289
self.authWith = "certificate"
93-
self.result = "proxy"
90+
self.response = "proxy"
9491
return S_OK()
9592

96-
def setCertificate(self, arg: str) -> dict:
97-
"""Set certificate file path
98-
99-
:param arg: path
100-
"""
101-
if not os.path.exists(arg):
102-
DIRAC.gLogger.error(f"{arg} does not exist.")
93+
def setCertificate(self, filePath: str) -> dict:
94+
"""Set certificate file path"""
95+
if not os.path.exists(filePath):
96+
DIRAC.gLogger.error(f"{filePath} does not exist.")
10397
DIRAC.exit(1)
10498
self.useCertificate(None)
105-
self.certLoc = arg
99+
self.certLoc = filePath
106100
return S_OK()
107101

108-
def setPrivateKey(self, arg: str) -> dict:
109-
"""Set private key file path
110-
111-
:param arg: path
112-
"""
113-
if not os.path.exists(arg):
114-
DIRAC.gLogger.error(f"{arg} is not exist.")
102+
def setPrivateKey(self, filePath: str) -> dict:
103+
"""Set private key file path"""
104+
if not os.path.exists(filePath):
105+
DIRAC.gLogger.error(f"{filePath} is not exist.")
115106
DIRAC.exit(1)
116107
self.useCertificate(None)
117-
self.keyLoc = arg
108+
self.keyLoc = filePath
118109
return S_OK()
119110

120-
def setOutputFile(self, arg: str) -> dict:
121-
"""Set output file location
122-
123-
:param arg: output file location
124-
"""
125-
self.outputFile = arg
111+
def setOutputFile(self, filePath: str) -> dict:
112+
"""Set output file location"""
113+
self.outputFile = filePath
126114
return S_OK()
127115

128-
def setLifetime(self, arg: str) -> dict:
129-
"""Set proxy lifetime
130-
131-
:param arg: lifetime
132-
"""
133-
self.lifetime = arg
116+
def setLifetime(self, lifetime: str) -> dict:
117+
"""Set proxy lifetime"""
118+
self.lifetime = lifetime
134119
return S_OK()
135120

136-
def setProxy(self, _arg) -> dict:
137-
"""Return proxy
138-
139-
:param _arg: unuse
140-
"""
121+
def setProxy(self, _) -> dict:
122+
"""Return proxy"""
141123
os.environ["DIRAC_USE_ACCESS_TOKEN"] = "false"
142-
self.result = "proxy"
124+
self.response = "proxy"
143125
return S_OK()
144126

145-
def setToken(self, _arg) -> dict:
146-
"""Return tokens
147-
148-
:param _arg: unuse
149-
"""
127+
def setToken(self, _) -> dict:
128+
"""Return tokens"""
150129
os.environ["DIRAC_USE_ACCESS_TOKEN"] = "true"
151130
self.useDIRACAS(None)
152-
self.result = "token"
131+
self.response = "token"
153132
return S_OK()
154133

155-
def authStatus(self, _arg) -> dict:
156-
"""Get authorization status
157-
158-
:param _arg: unuse
159-
"""
134+
def authStatus(self, _) -> dict:
135+
"""Get authorization status"""
160136
result = self.getAuthStatus()
161137
if result["OK"]:
162138
self.howToSwitch()
@@ -208,8 +184,8 @@ def doOAuthMagic(self):
208184
idpObj = result["Value"]
209185
if self.group and self.group not in self.scopes:
210186
self.scopes.append(f"g:{self.group}")
211-
if self.result == "proxy" and self.result not in self.scopes:
212-
self.scopes.append(self.result)
187+
if self.response == "proxy" and self.response not in self.scopes:
188+
self.scopes.append(self.response)
213189
if self.lifetime:
214190
self.scopes.append("lifetime:%s" % (int(self.lifetime or 12) * 3600))
215191
idpObj.scope = "+".join(self.scopes) if self.scopes else ""
@@ -219,8 +195,8 @@ def doOAuthMagic(self):
219195
if not result["OK"]:
220196
return result
221197

222-
if self.result == "proxy":
223-
self.outputFile = self.outputFile or Locations.getDefaultProxyLocation()
198+
if self.response == "proxy":
199+
self.outputFile = self.outputFile or getDefaultProxyLocation()
224200
# Save new proxy certificate
225201
result = writeToProxyFile(idpObj.token["proxy"].encode("UTF-8"), self.outputFile)
226202
if not result["OK"]:
@@ -261,15 +237,16 @@ def loginWithCertificate(self):
261237
"""Login with certificate"""
262238
# Search certificate and key
263239
if not self.certLoc or not self.keyLoc:
264-
cakLoc = Locations.getCertificateAndKeyLocation()
265-
if not cakLoc:
240+
if not (cakLoc := getCertificateAndKeyLocation()):
241+
if not self.authWith: # if user do not choose this way
242+
print(HTML("<yellow>Can't find user certificate and key</yellow>, trying to connact to DIRAC AS.."))
243+
return self.doOAuthMagic() # Then try to use DIRAC AS
266244
return S_ERROR("Can't find user certificate and key")
267245
self.certLoc = self.certLoc or cakLoc[0]
268246
self.keyLoc = self.keyLoc or cakLoc[1]
269247

270248
chain = X509Chain()
271249
# Load user cert and key
272-
273250
if (result := chain.loadChainFromFile(self.certLoc))["OK"]:
274251
# We try to download the key first without a password
275252
if not (result := chain.loadKeyFromFile(self.keyLoc))["OK"]:
@@ -288,17 +265,30 @@ def loginWithCertificate(self):
288265
proxy = copy.copy(chain)
289266

290267
# Create local proxy with group
291-
self.outputFile = self.outputFile or Locations.getDefaultProxyLocation()
292-
result = chain.generateProxyToFile(self.outputFile, int(self.lifetime or 12) * 3600, self.group)
268+
self.outputFile = self.outputFile or getDefaultProxyLocation()
269+
parameters = (self.outputFile, int(self.lifetime or 12) * 3600, self.group)
270+
271+
# Add a VOMS extension if the group requires it
272+
if (result := chain.generateProxyToFile(*parameters))["OK"] and (result := self.__enableCS())["OK"]:
273+
if not self.group and (result := findDefaultGroupForDN(credentials["DN"]))["OK"]:
274+
self.group = result["Value"] # Use default group if user don't set it
275+
# based on the configuration we decide whether to add VOMS extensions
276+
if getGroupOption(self.group, "AutoAddVOMS", False):
277+
if not (vomsAttr := getVOMSAttributeForGroup(self.group)):
278+
print(HTML(f"<yellow>No VOMS attribute foud for {self.group}</yellow>"))
279+
else:
280+
vo = getVOMSVOForGroup(self.group)
281+
if not (result := VOMS().setVOMSAttributes(chain, attribute=vomsAttr, vo=vo))["OK"]:
282+
return S_ERROR(f"Failed adding VOMS attribute: {result['Message']}")
283+
chain = result["Value"]
284+
result = chain.generateProxyToFile(*parameters)
293285
if not result["OK"]:
294286
return S_ERROR(f"Couldn't generate proxy: {result['Message']}")
295287

296288
if self.enableCS:
297289
# After creating the proxy, we can try to connect to the server
298-
result = Script.enableCS()
299-
if not result["OK"]:
300-
return S_ERROR(f"Cannot contact CS: {result['Message']}")
301-
gConfig.forceRefresh()
290+
if not (result := self.__enableCS())["OK"]:
291+
return result
302292

303293
# Step 2: Upload proxy to DIRAC server
304294
result = gProxyManager.getUploadedProxyLifeTime(credentials["subject"])
@@ -312,6 +302,11 @@ def loginWithCertificate(self):
312302
return gProxyManager.uploadProxy(proxy)
313303
return S_OK()
314304

305+
def __enableCS(self):
306+
if not (result := Script.enableCS())["OK"] or not (result := gConfig.forceRefresh())["OK"]:
307+
return S_ERROR(f"Cannot contact CS: {result['Message']}")
308+
return result
309+
315310
def howToSwitch(self) -> bool:
316311
"""Helper message, how to switch access type(proxy or access token)"""
317312
if "DIRAC_USE_ACCESS_TOKEN" in self.ENV:
@@ -326,21 +321,19 @@ def howToSwitch(self) -> bool:
326321
if src == "conf":
327322
msg += f" set /DIRAC/Security/UseTokens={not useTokens} in dirac.cfg\nor\n"
328323
msg += f" export DIRAC_USE_ACCESS_TOKEN={not useTokens}\n"
329-
gLogger.notice(msg)
330324

331-
return useTokens
325+
# Show infomation message only if the current state of the user environment does not match the authorization result
326+
if (useTokens and (self.response == "proxy")) or (not useTokens and (self.response == "token")):
327+
gLogger.notice(msg)
332328

333329
def getAuthStatus(self):
334330
"""Try to get user authorization status.
335-
336331
:return: S_OK()/S_ERROR()
337332
"""
338-
result = Script.enableCS()
339-
if not result["OK"]:
340-
return S_ERROR("Cannot contact CS.")
341-
gConfig.forceRefresh()
333+
if not (result := self.__enableCS())["OK"]:
334+
return result
342335

343-
if self.result == "proxy":
336+
if self.response == "proxy":
344337
result = getProxyInfo(self.outputFile)
345338
if result["OK"]:
346339
gLogger.notice(formatProxyInfoAsString(result["Value"]))
@@ -354,8 +347,8 @@ def getAuthStatus(self):
354347

355348
@Script()
356349
def main():
357-
p = Params()
358-
p.registerCLISwitches()
350+
userParams = Params()
351+
userParams.registerCLISwitches()
359352

360353
# Check time
361354
deviation = getClockDeviation()
@@ -375,24 +368,24 @@ def main():
375368
)
376369
DIRAC.exit(1)
377370

378-
p.group, p.scopes = Script.getPositionalArgs(group=True)
371+
userParams.group, userParams.scopes = Script.getPositionalArgs(group=True)
379372
# If you have chosen to use a certificate then a proxy will be generated locally using the specified certificate
380-
if p.authWith == "certificate":
381-
result = p.loginWithCertificate()
373+
if userParams.authWith == "certificate":
374+
result = userParams.loginWithCertificate()
382375

383376
# Otherwise, you must log in to the authorization server to gain access
384377
else:
385-
result = p.doOAuthMagic()
378+
result = userParams.doOAuthMagic()
386379

387380
# Print authorization status
388-
if result["OK"] and p.enableCS:
389-
result = p.getAuthStatus()
381+
if result["OK"] and userParams.enableCS:
382+
result = userParams.getAuthStatus()
390383

391384
if not result["OK"]:
392-
gLogger.fatal(result["Message"])
385+
print(HTML(f"<red>{result['Message']}</red>"))
393386
sys.exit(1)
394387

395-
p.howToSwitch()
388+
userParams.howToSwitch()
396389
sys.exit(0)
397390

398391

0 commit comments

Comments
 (0)