Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
root = true

[*]
charset = utf-8
tab_width = 4
indent_style = space

[*.py]
end_of_line = lf
indent_size = 4
trim_trailing_whitespace = true
insert_final_newline = true

[*.{yml,yaml}]
end_of_line = lf
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true
20 changes: 6 additions & 14 deletions .github/workflows/build-and-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ on:
push:
branches:
- main
- PILOT-2936

jobs:
extract-branch-name:
Expand Down Expand Up @@ -65,31 +64,24 @@ jobs:
- name: Build binary
run: poetry run pyinstaller -F --distpath ./app/bundled_app/linux --specpath ./app/build/linux --workpath ./app/build/linux --paths=./.venv/lib/python3.9/site-packages ./app/pilotcli.py -n ${{ github.sha }}

- name: Rename output file
run: mv "./app/bundled_app/linux/${{ github.sha }}" "./app/bundled_app/linux/pilotcli_linux"

- name: Set version in env
run: poetry run echo "TAG_VERSION=`poetry version --short`" >> $GITHUB_ENV

- name: Create Release
id: create_release
uses: actions/create-release@v1
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
with:
tag_name: ${{ env.TAG_VERSION }}
release_name: Release ${{ needs.extract-branch-name.outputs.branch }} ${{ env.TAG_VERSION }}
name: Release ${{ needs.extract-branch-name.outputs.branch }} ${{ env.TAG_VERSION }}
body: ${{ github.event.head_commit.message }}
draft: false
prerelease: false

- name: Upload Release Binary
id: upload-release-binary
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
asset_path: ./app/bundled_app/linux/${{ github.sha }}
asset_name: pilotcli_linux
asset_content_type: application/octet-stream
files: ./app/bundled_app/linux/pilotcli_linux

push-binary-macos:
needs: [ push-binary-linux ]
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ jobs:
uses: pre-commit/action@v3.0.0

- name: Run tests
run: poetry run pytest -vvv --exitfirst --cov=app --cov-report=term --cov-report=xml --cov-fail-under=23
run: poetry run pytest -vvv --exitfirst --cov=app --cov-report=term --cov-report=xml --cov-fail-under=54

- name: Coverage report comment
uses: mishakav/pytest-coverage-comment@v1.1.42
Expand Down
64 changes: 31 additions & 33 deletions app/commands/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
#
# Contact Indoc Research for any questions regarding the use of this source code.

from typing import Union

import click

import app.services.output_manager.help_page as user_help
import app.services.output_manager.message_handler as mhandler
from app.models.enums import LoginMethod
from app.services.user_authentication.decorator import require_login_session

# from app.services.user_authentication.user_login_logout import user_login
from app.services.user_authentication.user_login_logout import login_using_api_key
from app.services.user_authentication.user_login_logout import user_device_id_login
from app.services.user_authentication.user_login_logout import user_logout
from app.services.user_authentication.user_login_logout import validate_user_device_login
Expand All @@ -21,41 +23,37 @@ def cli():
pass


# COMMENT user password login temporally.
# @click.command()
# @click.option(
# '-U', '--username', prompt='Username', help=(user_help.user_help_page(user_help.UserHELP.USER_LOGIN_USERNAME))
# )
# @click.option(
# '-P',
# '--password',
# prompt='Password',
# help=(user_help.user_help_page(user_help.UserHELP.USER_LOGIN_PASSWORD)),
# hide_input=True,
# )
# @doc(user_help.user_help_page(user_help.UserHELP.USER_LOGIN))
# def login(username, password):
# user_login(username, password)
# mhandler.SrvOutPutHandler.login_success()


@click.command()
@click.option(
'--api-key',
envvar='PILOT_API_KEY',
help=(user_help.user_help_page(user_help.UserHELP.USER_LOGIN_API_KEY)),
)
@doc(user_help.user_help_page(user_help.UserHELP.USER_LOGIN))
def login():
device_login = user_device_id_login()
if device_login:
mhandler.SrvOutPutHandler.login_input_device_code(device_login['verification_uri_complete'])
mhandler.SrvOutPutHandler.login_device_code_qrcode(device_login['verification_uri_complete'])
def login(api_key: Union[str, None]):
if api_key:
mhandler.SrvOutPutHandler.login_using_method(LoginMethod.API_KEY)
is_valid = login_using_api_key(api_key)
if is_valid:
mhandler.SrvOutPutHandler.login_success()
else:
mhandler.SrvOutPutHandler.login_using_api_key_failed_error()
else:
mhandler.SrvOutPutHandler.login_input_device_error()
mhandler.SrvOutPutHandler.login_using_method(LoginMethod.DEVICE_CODE)
device_login = user_device_id_login()
if device_login:
mhandler.SrvOutPutHandler.login_input_device_code(device_login['verification_uri_complete'])
mhandler.SrvOutPutHandler.login_device_code_qrcode(device_login['verification_uri_complete'])
else:
mhandler.SrvOutPutHandler.login_input_device_error()

is_validated = validate_user_device_login(
device_login['device_code'], device_login['expires'], device_login['interval']
)
if is_validated:
mhandler.SrvOutPutHandler.login_success()
else:
mhandler.SrvOutPutHandler.validation_login_input_device_error()
is_validated = validate_user_device_login(
device_login['device_code'], device_login['expires'], device_login['interval']
)
if is_validated:
mhandler.SrvOutPutHandler.login_success()
else:
mhandler.SrvOutPutHandler.validation_login_input_device_error()


@click.command()
Expand Down
8 changes: 5 additions & 3 deletions app/configs/app_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
from env import ConfigClass


class AppConfig(object):
class Env(object):
class AppConfig:
class Env:
section = 'environment'
project = ConfigClass.project
user_config_path = ConfigClass.config_path
Expand Down Expand Up @@ -34,8 +34,9 @@ class Env(object):
greenroom_bucket_prefix = 'gr'

keycloak_device_client_id = ConfigClass.keycloak_device_client_id
keycloak_api_key_audience = ConfigClass.keycloak_api_key_audience

class Connections(object):
class Connections:
section = 'connections'
url_harbor = ConfigClass.url_harbor
url_authn = ConfigClass.url_authn
Expand All @@ -53,6 +54,7 @@ class Connections(object):
url_validation = ConfigClass.url_validation
url_keycloak = ConfigClass.url_keycloak
url_keycloak_token = f'{ConfigClass.url_keycloak}/token'
url_keycloak_realm = ConfigClass.url_keycloak.rstrip('/').replace('/protocol/openid-connect', '')
url_bff = ConfigClass.url_bff
# add url_base to check if value exist
url_base = ConfigClass.base_url
16 changes: 14 additions & 2 deletions app/configs/user_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@
import os
import time

from app.configs.app_config import AppConfig
from app.models.singleton import Singleton
from app.services.crypto.crypto import decryption
from app.services.crypto.crypto import encryption
from app.services.crypto.crypto import generate_secret

from .app_config import AppConfig


class UserConfig(metaclass=Singleton):
"""The class to maintain the user access/fresh token Note here: the base class is Singleton, meaning no matter how
Expand All @@ -32,6 +31,7 @@ def __init__(self):
self.config['USER'] = {
'username': '',
'password': '',
'api_key': '',
'access_token': '',
'refresh_token': '',
'secret': generate_secret(),
Expand All @@ -49,6 +49,7 @@ def clear(self):
self.config['USER'] = {
'username': '',
'password': '',
'api_key': '',
'access_token': '',
'refresh_token': '',
'hpc_token': '',
Expand All @@ -58,6 +59,9 @@ def clear(self):
}
self.save()

def is_logged_in(self) -> bool:
return bool(self.api_key or (self.access_token and self.refresh_token))

@property
def username(self):
return decryption(self.config['USER']['username'], self.secret)
Expand All @@ -74,6 +78,14 @@ def password(self):
def password(self, val):
self.config['USER']['password'] = encryption(val, self.secret)

@property
def api_key(self):
return decryption(self.config['USER']['api_key'], self.secret)

@api_key.setter
def api_key(self, val):
self.config['USER']['api_key'] = encryption(val, self.secret)

@property
def access_token(self):
return decryption(self.config['USER']['access_token'], self.secret)
Expand Down
12 changes: 12 additions & 0 deletions app/models/enums.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Copyright (C) 2023 Indoc Research
#
# Contact Indoc Research for any questions regarding the use of this source code.

from enum import Enum


class LoginMethod(str, Enum):
"""Available login methods."""

API_KEY = 'api-key'
DEVICE_CODE = 'device-code'
3 changes: 2 additions & 1 deletion app/resources/custom_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
class HelpPage:
page = {
'update': {
'version': '2.4.3',
'version': '2.5.1',
'1': 'The logic of normal upload and resumble are splited. '
'add new command for resumable upload as `pilotcli file resume -r manifest.json`',
'2': 'The manifest file will be output for both file/folder upload',
Expand All @@ -27,6 +27,7 @@ class HelpPage:
),
'USER_LOGIN_USERNAME': 'Specify username for login.',
'USER_LOGIN_PASSWORD': 'Specify password for login.',
'USER_LOGIN_API_KEY': 'Specify API Key for login.',
},
'file': {
'FILE_ATTRIBUTE_LIST': 'List attribute templates of a given Project.',
Expand Down
Loading