-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #117 from cedadev/quotas
Quotas initial implementation.
- Loading branch information
Showing
15 changed files
with
877 additions
and
104 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
NLDS quotas | ||
================================ | ||
|
||
Get the quota for your group in the NLDS. | ||
|
||
|
||
Implementation. | ||
------------------------ | ||
|
||
This is currently only implemented for the `jasmin_authenticator`. | ||
|
||
To get the quota, the `project_services_url` and `user_services_url` need to be present in the `jasmin_authenticator` section of the `server-config.rst` file. | ||
|
||
First, there is a call to the JASMIN Projects Portal to get information about the service. This call is made to the `project_services_url`. | ||
This is authorized on behalf of the NLDS using a client token, supplied in the config. | ||
The tape quota is then extracted from the service information. Only quota for allocated tape resource in a Group Workspace is returned, no other categories or status of resource (such as pending requests) are included. | ||
The quota command can be called from the `nlds client`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,9 +8,10 @@ | |
__license__ = "BSD - see LICENSE file in top-level package directory" | ||
__contact__ = "[email protected]" | ||
|
||
from .base_authenticator import BaseAuthenticator | ||
from ..server_config import load_config | ||
from ..utils.construct_url import construct_url | ||
from nlds.authenticators.base_authenticator import BaseAuthenticator | ||
from nlds.server_config import load_config | ||
from nlds.utils.format_url import format_url | ||
from nlds_processors.catalog.catalog_models import File, Holding, Transaction | ||
from retry import retry | ||
import requests | ||
import json | ||
|
@@ -25,6 +26,7 @@ def __init__(self): | |
self.config = load_config() | ||
self.name = "jasmin_authenticator" | ||
self.auth_name = "authentication" | ||
self.default_quota = 0 | ||
|
||
@retry(requests.ConnectTimeout, tries=5, delay=1, backoff=2) | ||
def authenticate_token(self, oauth_token: str): | ||
|
@@ -194,7 +196,7 @@ def authenticate_user_group_role(self, oauth_token: str, user: str, group: str): | |
"Authorization": f"Bearer {oauth_token}", | ||
} | ||
# Construct the URL | ||
url = construct_url( | ||
url = format_url( | ||
[config["user_grants_url"], user, "grants"], | ||
{"category": "GWS", "service": group}, | ||
) | ||
|
@@ -218,10 +220,9 @@ def authenticate_user_group_role(self, oauth_token: str, user: str, group: str): | |
if response.status_code == requests.codes.ok: # status code 200 | ||
try: | ||
response_json = json.loads(response.text) | ||
user_role = response_json["group_workspaces"] | ||
# is_manager is False by default and only changes if user has a manager or deputy role. | ||
is_manager = False | ||
for role in user_role: | ||
for role in response_json['group_workspaces']: | ||
if role in ["MANAGER", "DEPUTY"]: | ||
is_manager = True | ||
return is_manager | ||
|
@@ -237,3 +238,120 @@ def authenticate_user_group_role(self, oauth_token: str, user: str, group: str): | |
) | ||
else: | ||
return False | ||
|
||
|
||
@staticmethod | ||
def user_has_get_holding_permission(user: str, | ||
group: str, | ||
holding: Holding) -> bool: | ||
"""Check whether a user has permission to view this holding. | ||
When we implement ROLES this will be more complicated.""" | ||
permitted = True | ||
#Users can view / get all holdings in their group | ||
#permitted &= holding.user == user | ||
permitted &= holding.group == group | ||
return permitted | ||
|
||
|
||
def user_has_get_file_permission(session, | ||
user: str, | ||
group: str, | ||
file: File) -> bool: | ||
"""Check whether a user has permission to access a file. | ||
Later, when we implement the ROLES this function will be a lot more | ||
complicated!""" | ||
assert(session != None) | ||
holding = session.query(Holding).filter( | ||
Transaction.id == file.transaction_id, | ||
Holding.id == Transaction.holding_id | ||
).all() | ||
permitted = True | ||
for h in holding: | ||
# users have get file permission if in group | ||
# permitted &= h.user == user | ||
permitted &= h.group == group | ||
|
||
return permitted | ||
|
||
|
||
def user_has_delete_from_holding_permission(self, user: str, | ||
group: str, | ||
holding: Holding) -> bool: | ||
"""Check whether a user has permission to delete files from this holding. | ||
When we implement ROLES this will be more complicated.""" | ||
# is_admin == whether the user is an administrator of the group | ||
# i.e. a DEPUTY or MANAGER | ||
# this gives them delete permissions for all files in the group | ||
is_admin = self.authenticate_user_group_role(user, group) | ||
permitted = True | ||
# Currently, only users can delete files from their owned holdings | ||
permitted &= (holding.user == user or is_admin) | ||
permitted &= holding.group == group | ||
return permitted | ||
|
||
|
||
@retry(requests.ConnectTimeout, tries=5, delay=1, backoff=2) | ||
def get_service_information(self, service_name: str): | ||
"""Make a call to the JASMIN Projects Portal to get the service information.""" | ||
config = self.config[self.auth_name][self.name] | ||
token_headers = { | ||
"Content-Type": "application/x-ww-form-urlencoded", | ||
"cache-control": "no-cache", | ||
# WORK THIS OUT | ||
"Authorization": f"Bearer {config["client_token"]}", | ||
} | ||
# Contact the user_services_url to get the information about the services | ||
url = format_url([config["project_services_url"]], {"name": service_name}) | ||
print(url) | ||
try: | ||
response = requests.get( | ||
url, | ||
headers=token_headers, | ||
timeout=JasminAuthenticator._timeout, | ||
) | ||
except requests.exceptions.ConnectionError: | ||
raise RuntimeError(f"User services url {url} could not be reached.") | ||
except KeyError: | ||
raise RuntimeError(f"Could not find 'user_services_url' key in the {self.name} section of the .server_config file.") | ||
if response.status_code == requests.codes.ok: # status code 200 | ||
try: | ||
response_json = json.loads(response.text) | ||
return response_json | ||
except json.JSONDecodeError: | ||
raise RuntimeError(f"Invalid JSON returned from the user services url: {url}") | ||
else: | ||
raise RuntimeError(f"Error getting data for {service_name}") | ||
|
||
|
||
def get_tape_quota(self, service_name: str): | ||
"""Get the service information then process it to extract the quota for the service.""" | ||
try: | ||
result = self.get_service_information(service_name) | ||
except (RuntimeError, ValueError) as e: | ||
raise type(e)(f"Error getting information for {service_name}: {e}") | ||
|
||
try: | ||
# Filter for Group Workspace category | ||
group_workspace = next( | ||
service for service in result if service.get("category") == 1 | ||
) | ||
except StopIteration: | ||
raise ValueError(f"Cannot find a Group workspace with the name {service_name}. Check the category.") | ||
|
||
requirements = group_workspace.get("requirements") | ||
if not requirements: | ||
raise ValueError(f"Cannot find any requirements for {service_name}.") | ||
|
||
tape_quota = next( | ||
( | ||
req.get("amount") | ||
for req in requirements | ||
if req.get("status") == 50 and req.get("resource", {}).get("short_name") == "tape" | ||
), | ||
None, | ||
) | ||
|
||
if tape_quota is not None: | ||
return tape_quota | ||
else: | ||
return self.default_quota |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.