Skip to content

Commit 7d89902

Browse files
Rasmus Oscar Welanderglpatcern
authored andcommitted
Added authentication and exceptions
1 parent fd46dd1 commit 7d89902

File tree

2 files changed

+192
-0
lines changed

2 files changed

+192
-0
lines changed

src/auth.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
"""
2+
auth.py
3+
4+
Authors: Rasmus Welander, Diogo Castro, Giuseppe Lo Presti.
5+
6+
Last updated: 29/07/2024
7+
"""
8+
9+
from typing import List
10+
import grpc
11+
import jwt
12+
import datetime
13+
import logging
14+
import cs3.gateway.v1beta1.gateway_api_pb2 as gw
15+
from cs3.auth.registry.v1beta1.registry_api_pb2 import ListAuthProvidersRequest
16+
from cs3.gateway.v1beta1.gateway_api_pb2_grpc import GatewayAPIStub
17+
from cs3.rpc.v1beta1.code_pb2 import CODE_OK
18+
19+
from exceptions.exceptions import AuthenticationException, SecretNotSetException
20+
from config import Config
21+
22+
23+
class Auth:
24+
"""
25+
Auth class to handle authentication and token validation with CS3 Gateway API.
26+
"""
27+
28+
def __init__(self, config: Config, log: logging.Logger, gateway: GatewayAPIStub) -> None:
29+
"""
30+
Initializes the Auth class with configuration, logger, and gateway stub,
31+
NOTE that token OR the client secret has to be set when instantiating the auth object.
32+
33+
:param config: Config object containing the configuration parameters.
34+
:param log: Logger instance for logging.
35+
:param gateway: GatewayAPIStub instance for interacting with CS3 Gateway.
36+
"""
37+
self._gateway: GatewayAPIStub = gateway
38+
self._log: logging.Logger = log
39+
self._config: Config = config
40+
# The user should be able to change the client secret (e.g. token) at runtime
41+
self._client_secret: str | None = None
42+
self._token: str | None = None
43+
44+
def set_token(self, token: str) -> None:
45+
"""
46+
Should be used if the user wishes to set the reva token directly, instead of letting the client
47+
exchange credentials for the token. NOTE that token OR the client secret has to be set when
48+
instantiating the client object.
49+
50+
:param token: The reva token.
51+
"""
52+
self._token = token
53+
54+
def set_client_secret(self, token: str) -> None:
55+
"""
56+
Sets the client secret, exists so that the user can change the client secret (e.g. token, password) at runtime,
57+
without having to create a new Auth object. NOTE that token OR the client secret has to be set when
58+
instantiating the client object.
59+
60+
:param token: Auth token/password.
61+
"""
62+
self._client_secret = token
63+
64+
def get_token(self) -> tuple[str, str]:
65+
"""
66+
Attempts to get a valid authentication token. If the token is not valid, a new token is requested
67+
if the client secret is set, if only the token is set then an exception will be thrown stating that
68+
the credentials have expired.
69+
70+
:return tuple: A tuple containing the header key and the token.
71+
May throw AuthenticationException (token expired, or failed to authenticate)
72+
or SecretNotSetException (neither token or client secret was set).
73+
"""
74+
75+
if not Auth._check_token(self._token):
76+
# Check that client secret or token is set
77+
if not self._client_secret and not self._token:
78+
self._log.error("Attempted to authenticate, neither client secret or token was set.")
79+
raise SecretNotSetException("")
80+
elif not self._client_secret and self._token:
81+
# Case where ONLY a token is provided but it has expired
82+
self._log.error("The provided token have expired")
83+
raise AuthenticationException("The credentials have expired")
84+
# Create an authentication request
85+
req = gw.AuthenticateRequest(
86+
type=self._config.auth_login_type,
87+
client_id=self._config.auth_client_id,
88+
client_secret=self._client_secret,
89+
)
90+
# Send the authentication request to the CS3 Gateway
91+
res = self._gateway.Authenticate(req)
92+
93+
if res.status.code != CODE_OK:
94+
self._log.error(
95+
f"Failed to authenticate user {self._config.auth_client_id}, error: {res.status.message}"
96+
)
97+
raise AuthenticationException(
98+
f"Failed to authenticate user {self._config.auth_client_id}, error: {res.status.message}"
99+
)
100+
self._token = res.token
101+
return ("x-access-token", self._token)
102+
103+
def list_auth_providers(self) -> List[str]:
104+
"""
105+
list authentication providers
106+
107+
:return: a list of the supported authentication types
108+
May return ConnectionError (Could not connect to host)
109+
"""
110+
try:
111+
res = self._gateway.ListAuthProviders(request=ListAuthProvidersRequest())
112+
if res.status.code != CODE_OK:
113+
self._log.error(f"List auth providers request failed, error: {res.status.message}")
114+
raise Exception(res.status.message)
115+
except grpc.RpcError as e:
116+
self._log.error("List auth providers request failed")
117+
raise ConnectionError(e)
118+
return res.types
119+
120+
@classmethod
121+
def _check_token(cls, token: str) -> bool:
122+
"""
123+
Checks if the given token is set and valid.
124+
125+
:param token: JWT token as a string.
126+
:return: True if the token is valid, False otherwise.
127+
"""
128+
if not token:
129+
return False
130+
# Decode the token without verifying the signature
131+
decoded_token = jwt.decode(jwt=token, algorithms=["HS256"], options={"verify_signature": False})
132+
now = datetime.datetime.now().timestamp()
133+
token_expiration = decoded_token.get("exp")
134+
if token_expiration and now > token_expiration:
135+
return False
136+
137+
return True

src/exceptions/exceptions.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""
2+
exceptions.py
3+
4+
Custom exception classes for the CS3 client.
5+
6+
Authors: Rasmus Welander, Diogo Castro, Giuseppe Lo Presti.
7+
8+
Last updated: 29/07/2024
9+
"""
10+
11+
12+
class AuthenticationException(Exception):
13+
"""
14+
Standard error thrown when attempting an operation without the required access rights
15+
"""
16+
17+
def __init__(self, message: str = ""):
18+
super().__init__("Operation not permitted" + " " + message)
19+
20+
21+
class NotFoundException(IOError):
22+
"""
23+
Standard file missing message
24+
"""
25+
26+
def __init__(self, message: str = ""):
27+
super().__init__("No such file or directory" + " " + message)
28+
29+
30+
class SecretNotSetException(Exception):
31+
"""
32+
Standard file missing message
33+
"""
34+
35+
def __init__(self, message: str = ""):
36+
super().__init__("The client secret (e.g. token, passowrd) is not set" + " " + message)
37+
38+
39+
class FileLockedException(IOError):
40+
"""
41+
Standard error thrown when attempting to overwrite a file/xattr in O_EXCL mode
42+
or when a lock operation cannot be performed because of failed preconditions
43+
"""
44+
45+
def __init__(self, message: str = ""):
46+
super().__init__("File/xattr exists but EXCL mode requested, lock mismatch or lock expired" + " " + message)
47+
48+
49+
class UnknownException(Exception):
50+
"""
51+
Standard exception to be thrown when we get an error that is unknown, e.g. not defined in the cs3api
52+
"""
53+
54+
def __init__(self, message: str = ""):
55+
super().__init__(message)

0 commit comments

Comments
 (0)