Skip to content

Commit

Permalink
feat: add credentials command to store encrypted credentials in a ded…
Browse files Browse the repository at this point in the history
…icated config file (#60)
  • Loading branch information
CGuichard committed Dec 21, 2021
1 parent d7968ae commit ce52ea2
Show file tree
Hide file tree
Showing 2 changed files with 225 additions and 0 deletions.
202 changes: 202 additions & 0 deletions eodag/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
noqa: D103
"""
import getpass
import json
import os
import shutil
Expand All @@ -48,9 +49,16 @@
import click

from eodag.api.core import DEFAULT_ITEMS_PER_PAGE, DEFAULT_PAGE, EODataAccessGateway
from eodag.config import (
get_provider_credentials,
save_providers_credentials,
set_provider_credentials_key,
)
from eodag.utils import parse_qs
from eodag.utils.cli import ask_confirmation
from eodag.utils.exceptions import NoMatchingProductType, UnsupportedProvider
from eodag.utils.logging import setup_logging
from eodag.utils.security import Crypto

# A list of supported crunchers that the user can choose (see --cruncher option below)
CRUNCHERS = [
Expand Down Expand Up @@ -830,5 +838,199 @@ def deploy_wsgi_app(
click.echo(apache_config_sample)


@eodag.command(
name="credentials",
help="This command can be used to create, read, update, or delete a provider's credentials. "
"Default behavior is create/update.",
)
@click.option(
"-f",
"--conf",
type=click.Path(exists=True),
help="File path to the user configuration file, default is ~/.config/eodag/eodag.yml",
)
@click.option(
"-c",
"--creds",
type=click.Path(exists=False),
help="File path to the credentials file, default is ~/.config/eodag/credentials",
)
@click.option(
"-y",
"--yes",
is_flag=True,
help="Answers yes to command confirmation, disabling safety of credentials update, read and deletion",
)
@click.option(
"-l",
"--list",
is_flag=True,
help="Display the provider's credentials fields names",
)
@click.option(
"-e",
"--exists",
is_flag=True,
help="Check if the provider's credentials exists in the credentials file. "
"WARNING: Raw credentials in the user conf are not checked",
)
@click.option(
"-s",
"--set",
multiple=True,
help="Set a provider's credentials value",
)
@click.option(
"-r",
"--read",
is_flag=True,
help="Read the provider's credentials and display them. WARNING: "
"This command may display sensible informations, be wary of it",
)
@click.option(
"-d",
"--delete",
is_flag=True,
help="Delete the provider's credentials",
)
@click.argument("provider", type=str)
@click.pass_context
def credentials(ctx, **kwargs):
"""Command used to create, read, update, or delete a provider's credentials, Default behavior is create/update."""
setup_logging(verbose=ctx.obj["verbosity"])

conf_file = kwargs.pop("conf")
if conf_file:
conf_file = click.format_filename(conf_file)
creds_file = kwargs.pop("creds")
if creds_file:
creds_file = click.format_filename(creds_file)

dag = EODataAccessGateway(
user_conf_file_path=conf_file, credentials_file_path=creds_file
)
providers = dag.available_providers()
provider = kwargs.pop("provider")

if provider not in providers:
click.echo(
f"Error: Provider '{provider}' not found in available providers.",
err=True,
)
sys.exit(-1)

provider_credentials = get_provider_credentials(dag.providers_config[provider])
encrypted_credentials = {**dag.encrypted_credentials}
provider_has_encrypted_credentials = provider in dag.encrypted_credentials

do_list = kwargs.pop("list")
if do_list:
creds_names = list(provider_credentials.keys())
click.echo(
f"Credentials field{'s' if len(creds_names)>1 else ''} for '{provider}': ",
nl=False,
)
click.echo(", ".join(creds_names))
sys.exit(0)

do_exists = kwargs.pop("exists")
if do_exists:
if provider_has_encrypted_credentials:
click.echo(f"Credentials found for provider '{provider}'.")
sys.exit(0)
else:
click.echo(
f"Error: No credentials found for the provider '{provider}' inside '{dag.credentials_file_path}'.",
err=True,
)
sys.exit(-1)

confirm_yes = kwargs.pop("yes")

do_read = kwargs.pop("read")
if do_read:
if provider in dag.encrypted_credentials:
if not confirm_yes:
confirm = ask_confirmation(
f"Do you want to read the credentials saved for '{provider}' "
f"inside '{dag.credentials_file_path}'?"
)
if not confirm:
sys.exit(1)
for cred_name, cred in provider_credentials.items():
click.echo(f"{cred_name}: {cred}")
sys.exit(0)
else:
click.echo(
f"Error: No credentials found for the provider '{provider}' "
f"inside '{dag.credentials_file_path}', cannot read."
"",
err=True,
)
sys.exit(-1)

do_delete = kwargs.pop("delete")
if do_delete:
if provider in dag.encrypted_credentials:
if not confirm_yes:
confirm = ask_confirmation(
f"Do you want to delete the credentials saved for '{provider}' "
f"inside '{dag.credentials_file_path}'?"
)
if not confirm:
sys.exit(1)
del encrypted_credentials[provider]
save_providers_credentials(dag.credentials_file_path, encrypted_credentials)
click.echo(f"Credentials of '{provider}' were deleted.")
sys.exit(0)
else:
click.echo(
f"Error: No credentials found for the provider '{provider}' "
f"inside '{dag.credentials_file_path}', cannot delete.",
err=True,
)
sys.exit(-1)

if provider in dag.encrypted_credentials and not confirm_yes:
confirm = ask_confirmation(
f"The provider '{provider}' already have credentials saved inside '{dag.credentials_file_path}', "
"do you want to override them?"
)
if not confirm:
sys.exit(1)

crypto = Crypto()
new_creds = {}

creds_from_cli = kwargs.pop("set")
if creds_from_cli:
for cred_def in creds_from_cli:
cred_name, *cred_parts = str(cred_def).split("=", 1)
cred = "".join(cred_parts)
if cred and cred_name in provider_credentials:
new_creds[cred_name] = crypto.encrypt(cred)

remaining = list(filter(lambda i: i not in new_creds, provider_credentials))
if remaining:
click.echo(
f"Error: Missing credentials field{'s' if len(remaining)>1 else ''}: {', '.join(remaining)}.",
err=True,
)
sys.exit(-1)
else:
click.echo(f"Enter your credentials for the provider '{provider}':")
for cred_name in provider_credentials:
cred_input = getpass.getpass(prompt=f"- {cred_name}: ")
new_creds[cred_name] = crypto.encrypt(cred_input)

set_provider_credentials_key(new_creds, crypto.key)
encrypted_credentials[provider] = new_creds

save_providers_credentials(dag.credentials_file_path, encrypted_credentials)
click.echo(
f"Credentials for '{provider}' saved inside '{dag.credentials_file_path}'."
)


if __name__ == "__main__":
eodag(obj={})
23 changes: 23 additions & 0 deletions eodag/utils/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Copyright 2021, CS GROUP - France, https://www.csgroup.eu/
#
# This file is part of EODAG project
# https://www.github.com/CS-SI/EODAG
#
# 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.


def ask_confirmation(question):
"""Confirmation input for question"""
ask = input(f"{question} [y/n] ").strip().lower()
return ask in ["y", "Y"]

0 comments on commit ce52ea2

Please sign in to comment.