-
-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch '2.0-alpha1' into 2.0-alpha1-update
- Loading branch information
Showing
16 changed files
with
1,109 additions
and
259 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,156 @@ | ||
""" | ||
Class for managing tokens | ||
""" | ||
|
||
import jwt | ||
import liboidcagent as agent | ||
import requests | ||
|
||
from fedcloudclient.conf import CONF as CONF | ||
from fedcloudclient.exception import TokenError | ||
from fedcloudclient.logger import log_and_raise | ||
|
||
|
||
class Token: | ||
""" | ||
Abstract object for managing tokens | ||
""" | ||
|
||
def get_token(self): | ||
... | ||
|
||
def get_token_type(self): | ||
... | ||
|
||
|
||
class OIDCToken(Token): | ||
""" | ||
OIDC tokens. Managing access tokens, oidc-agent account and mytoken | ||
""" | ||
|
||
def __init__(self, access_token=None): | ||
super().__init__() | ||
self.access_token = access_token | ||
self.payload = None | ||
self.oidc_agent_account = None | ||
self.mytoken = None | ||
self.user_id = None | ||
|
||
def get_token(self): | ||
""" | ||
Return access token or raise error | ||
:return: | ||
""" | ||
if self.access_token: | ||
return self.access_token | ||
else: | ||
error_msg = "Token is not initialized" | ||
log_and_raise(error_msg, TokenError) | ||
|
||
def decode_token(self) -> dict: | ||
""" | ||
Decoding access token to payload | ||
:return: | ||
""" | ||
if not self.payload: | ||
try: | ||
self.payload = jwt.decode(self.access_token, options={"verify_signature": False}) | ||
self.user_id = self.payload["sub"] | ||
except jwt.exceptions.InvalidTokenError: | ||
error_msg = "Invalid access token" | ||
log_and_raise(error_msg, TokenError) | ||
|
||
return self.payload | ||
|
||
def get_user_id(self) -> str: | ||
""" | ||
Return use ID | ||
:return: | ||
""" | ||
if not self.payload: | ||
self.decode_token() | ||
return self.user_id | ||
|
||
def get_token_from_oidc_agent(self, oidc_agent_account: str) -> str: | ||
""" | ||
Get access token from oidc-agent | ||
:param oidc_agent_account: account name in oidc-agent | ||
:return: access token, and set internal token, raise TokenError on None | ||
""" | ||
|
||
if oidc_agent_account: | ||
try: | ||
access_token = agent.get_access_token( | ||
oidc_agent_account, | ||
min_valid_period=CONF.get("min_access_token_time"), | ||
application_hint="fedcloudclient", | ||
) | ||
self.access_token = access_token | ||
self.oidc_agent_account = oidc_agent_account | ||
return access_token | ||
except agent.OidcAgentError as exception: | ||
error_msg = f"Error getting access token from oidc-agent: {exception}" | ||
log_and_raise(error_msg, TokenError) | ||
|
||
else: | ||
error_msg = f"Error getting access token from oidc-agent: account name is {oidc_agent_account}" | ||
log_and_raise(error_msg, TokenError) | ||
|
||
def get_token_from_mytoken(self, mytoken: str, mytoken_server: str = None) -> str: | ||
""" | ||
Get access token from mytoken server | ||
:param mytoken: | ||
:param mytoken_server: | ||
:return: access token, or None on error | ||
""" | ||
if not mytoken_server: | ||
mytoken_server = CONF.get("mytoken_server") | ||
|
||
if mytoken: | ||
try: | ||
data = { | ||
"grant_type": "mytoken", | ||
"mytoken": mytoken, | ||
} | ||
req = requests.post( | ||
mytoken_server + "/api/v0/token/access", | ||
json=data, | ||
) | ||
req.raise_for_status() | ||
access_token = req.json().get("access_token") | ||
self.access_token = access_token | ||
self.mytoken = mytoken | ||
return access_token | ||
|
||
except requests.exceptions.HTTPError as exception: | ||
error_msg = f"Error getting access token from mytoken server: {exception}" | ||
log_and_raise(error_msg, TokenError) | ||
|
||
else: | ||
error_msg = f"Error getting access token from mytoken server: mytoken is {mytoken}" | ||
log_and_raise(error_msg, TokenError) | ||
|
||
def multiple_token(self, access_token: str, oidc_agent_account: str, mytoken: str): | ||
""" | ||
Select valid token from multiple options | ||
:param access_token: | ||
:param oidc_agent_account: | ||
:param mytoken: | ||
:return: | ||
""" | ||
if mytoken: | ||
try: | ||
self.get_token_from_mytoken(mytoken) | ||
return | ||
except TokenError: | ||
pass | ||
if oidc_agent_account: | ||
try: | ||
self.get_token_from_oidc_agent(oidc_agent_account) | ||
return | ||
except TokenError: | ||
pass | ||
if access_token: | ||
self.access_token = access_token | ||
return | ||
log_and_raise("Cannot get access token", TokenError) |
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,24 @@ | ||
""" | ||
Testing unit for auth.py | ||
""" | ||
import os | ||
|
||
import fedcloudclient.auth as auth | ||
|
||
|
||
def get_token_from_mytoken_decode_verify(mytoken: str, user_id: str): | ||
""" | ||
Get access token from mytoken server, decode, get user ID and verify | ||
:return: | ||
""" | ||
|
||
token = auth.OIDCToken() | ||
token.get_token_from_mytoken(mytoken) | ||
token_id = token.get_user_id() | ||
assert token_id == user_id | ||
|
||
|
||
if __name__ == "__main__": | ||
mytoken = os.environ["FEDCLOUD_MYTOKEN"] | ||
user_id = os.environ["FEDCLOUD_ID"] | ||
get_token_from_mytoken_decode_verify(mytoken, user_id) |
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 |
---|---|---|
@@ -0,0 +1,160 @@ | ||
""" | ||
Read/write configuration files | ||
""" | ||
import json | ||
import os | ||
import sys | ||
from pathlib import Path | ||
|
||
import click | ||
import yaml | ||
from tabulate import tabulate | ||
|
||
from fedcloudclient.exception import ConfigError | ||
|
||
DEFAULT_CONFIG_LOCATION = Path.home() / ".config/fedcloud/config.yaml" | ||
DEFAULT_SETTINGS = { | ||
"site": "IISAS-FedCloud", | ||
"vo": "vo.access.egi.eu", | ||
"site_list_url": "https://raw.githubusercontent.com/tdviet/fedcloudclient/master/config/sites.yaml", | ||
"site_dir": str(Path.home() / ".config/fedcloud/site-config/"), | ||
"oidc_url": "https://aai.egi.eu/auth/realms/egi", | ||
"openstack_auth_protocol": "openid", | ||
"openstack_auth_provider": "egi.eu", | ||
"openstack_auth_type": "v3oidcaccesstoken", | ||
"gocdb_public_url": "https://goc.egi.eu/gocdbpi/public/", | ||
"gocdb_service_group": "org.openstack.nova", | ||
"vault_endpoint": "https://vault.services.fedcloud.eu:8200", | ||
"vault_role": "", | ||
"vault_mount_point": "/secrets/", | ||
"vault_locker_mount_point": "/v1/cubbyhole/", | ||
"vault_salt": "fedcloud_salt", | ||
"log_file": str(Path.home() / ".config/fedcloud/logs/fedcloud.log"), | ||
"log_level": "DEBUG", | ||
"log_config_file": str(Path.home() / ".config/fedcloud/logging.conf"), | ||
"requests_cert_file": str(Path.home() / ".config/fedcloud/cert/certs.pem"), | ||
"oidc_agent_account": "egi", | ||
"min_access_token_time": 30, | ||
"mytoken_server": "https://mytoken.data.kit.edu", | ||
} | ||
|
||
|
||
def save_config(filename: str, config_data: dict): | ||
""" | ||
Save configuration to file | ||
:param filename: name of config file | ||
:param config_data: dict containing configuration | ||
:return: None | ||
""" | ||
config_file = Path(filename).resolve() | ||
try: | ||
with config_file.open(mode="w+", encoding="utf-8") as file: | ||
yaml.dump(config_data, file) | ||
except yaml.YAMLError as exception: | ||
error_msg = f"Error during saving configuration to {filename}: {exception}" | ||
raise ConfigError(error_msg) | ||
|
||
|
||
def load_config(filename: str) -> dict: | ||
""" | ||
Load configuration file | ||
:param filename: | ||
:return: configuration data | ||
""" | ||
|
||
config_file = Path(filename).resolve() | ||
|
||
if config_file.is_file(): | ||
try: | ||
with config_file.open(mode="r", encoding="utf-8") as file: | ||
return yaml.safe_load(file) | ||
except yaml.YAMLError as exception: | ||
error_msg = f"Error during loading configuration from {filename}: {exception}" | ||
raise ConfigError(error_msg) | ||
else: | ||
return {} | ||
|
||
|
||
def load_env() -> dict: | ||
""" | ||
Load configs from environment variables | ||
:return: config | ||
""" | ||
env_config = dict() | ||
for env in os.environ: | ||
if env.startswith("FEDCLOUD_"): | ||
config_key = env[9:].lower() | ||
env_config[config_key] = os.environ[env] | ||
return env_config | ||
|
||
|
||
def init_config() -> dict: | ||
""" | ||
Init config moduls | ||
:return: actual config | ||
""" | ||
env_config = load_env() | ||
config_file = env_config.get("config_file", DEFAULT_CONFIG_LOCATION) | ||
|
||
try: | ||
saved_config = load_config(config_file) | ||
except ConfigError as exception: | ||
print(f"Error while loading config: {exception}") | ||
saved_config = {} | ||
|
||
act_config = {**DEFAULT_SETTINGS, **saved_config, **env_config} | ||
return act_config | ||
|
||
|
||
@click.group() | ||
def config(): | ||
""" | ||
Managing fedcloud configurations | ||
""" | ||
|
||
|
||
@config.command() | ||
@click.option( | ||
"--config-file", | ||
type=click.Path(dir_okay=False), | ||
default=DEFAULT_CONFIG_LOCATION, | ||
help="configuration file", | ||
envvar="FEDCLOUD_CONFIG_FILE", | ||
show_default=True, | ||
) | ||
def create(config_file: str): | ||
"""Create default configuration file""" | ||
save_config(config_file, DEFAULT_SETTINGS) | ||
print(f"Default configuration is saved in {config_file}") | ||
|
||
|
||
@config.command() | ||
@click.option( | ||
"--config-file", | ||
type=click.Path(dir_okay=False), | ||
default=DEFAULT_CONFIG_LOCATION, | ||
help="configuration file", | ||
envvar="FEDCLOUD_CONFIG_FILE", | ||
show_default=True, | ||
) | ||
@click.option( | ||
"--output-format", | ||
"-f", | ||
required=False, | ||
help="Output format", | ||
type=click.Choice(["text", "YAML", "JSON"], case_sensitive=False), | ||
) | ||
def show(config_file: str, output_format: str): | ||
"""Show actual client configuration """ | ||
saved_config = load_config(config_file) | ||
env_config = load_env() | ||
act_config = {**DEFAULT_SETTINGS, **saved_config, **env_config} | ||
if output_format == "YAML": | ||
yaml.dump(act_config, sys.stdout, sort_keys=False) | ||
elif output_format == "JSON": | ||
json.dump(act_config, sys.stdout, indent=4) | ||
else: | ||
print(tabulate(act_config.items(), headers=["parameter", "value"])) | ||
|
||
|
||
CONF = init_config() |
Oops, something went wrong.