New API for dev mode (dummy platform), dev tools, auth cmd prototype #9

Merged
MOIS3Y merged 6 commits from MOIS3Y/isp-maintenance:api into main 2024-06-04 23:53:30 +08:00
8 changed files with 256 additions and 164 deletions
Showing only changes of commit 72bfeb9193 - Show all commits

View File

@ -1,59 +0,0 @@
# ? docker-compose.yml for development environment
# ! To start development you need to create a directory ./dummy_platform.
# ? Place files from the test platform into it:
# ? VM6:
# ? /opt/ispsystem/vm/config.json - configuration file
# ? /opt/ispsystem/vm/mysql - database directory
# ? DCI6:
# ? /opt/ispsystem/dci/config.json - configuration file
# ? /opt/ispsystem/dci/mysql - database directory
# ? Create ./.env file and fill it with required vars:
# ? PLATFORM_TYPE='vm'
# ? Database container:
# ? MYSQL_DATABASE="database name"
# ? MYSQL_ROOT_PASSWORD="super secret password from config.json"
# ? Launch:
# ? docker-compose up -d --force-recreate
# ? docker attach mgrctl
services:
mgrctl:
container_name: mgrctl
restart: unless-stopped
build:
context: .
args:
- APP_VERSION=${APP_VERSION}
- APP_DIR=${APP_DIR}
- SRC_DIR=${SRC_DIR}
- PKG_NAME=${PKG_NAME}
- PKG_VERSION=${PKG_VERSION}
networks:
vm_box_net: null
volumes:
- type: bind
source: ./dummy_platform/opt/ispsystem/${PLATFORM_TYPE}/config.json
target: /opt/ispsystem/${PLATFORM_TYPE}/config.json
env_file:
- ./.env
tty: true
stdin_open: true
mysql:
container_name: mysql
image: docker-registry.ispsystem.com/mysql:5
volumes:
- ./dummy_platform/opt/ispsystem/${PLATFORM_TYPE}/mysql:/var/lib/mysql
env_file:
- ./.env
labels:
autoconf_mysql: "true"
networks:
vm_box_net: null
command: --group-concat-max-len=131072 --max-connections=1000 --optimizer-search-depth=0
networks:
vm_box_net:
driver: bridge

View File

@ -3,18 +3,52 @@ import json
import urllib
import requests
from mgrctl.settings.api import INPUT_HOSTNAME, INPUT_PORT, HEADERS
from mgrctl.settings.api import INPUT_URL, HEADERS
from mgrctl.settings.platform import (
PLATFORM_TYPE,
PLATFORM_VERIFY_SSL,
PLATFORM_DUMMY,
PLATFORM_DUMMY_VM6_API_URL,
PLATFORM_DUMMY_VM6_EMAIL,
PLATFORM_DUMMY_VM6_PASSWORD,
PLATFORM_DUMMY_VM6_TOKEN,
PLATFORM_DUMMY_DCI6_API_URL,
PLATFORM_DUMMY_DCI6_EMAIL,
PLATFORM_DUMMY_DCI6_PASSWORD,
PLATFORM_DUMMY_DCI6_TOKEN
)
class BaseAPI(object):
def __init__(self, api_url=None, verify_ssl=True):
def __init__(self):
"""Announces required parameters"""
internal_api_url = f'http://{INPUT_HOSTNAME}:{INPUT_PORT}'
self.API_URL = api_url if api_url else internal_api_url
if PLATFORM_TYPE == 'vm':
if PLATFORM_DUMMY:
self.API_URL = PLATFORM_DUMMY_VM6_API_URL
self.AUTH_TYPE = 'Public'
self.HEADERS = {'x-xsrf-token': PLATFORM_DUMMY_VM6_TOKEN}
self.EMAIL = PLATFORM_DUMMY_VM6_EMAIL
self.PASSWORD = PLATFORM_DUMMY_VM6_PASSWORD
else:
self.API_URL = INPUT_URL
self.AUTH_TYPE = 'Internal'
self.HEADERS = HEADERS
if PLATFORM_TYPE == 'dci':
if PLATFORM_DUMMY:
self.API_URL = PLATFORM_DUMMY_DCI6_API_URL
self.AUTH_TYPE = 'Public'
self.HEADERS = {'x-xsrf-token': PLATFORM_DUMMY_DCI6_TOKEN}
self.EMAIL = PLATFORM_DUMMY_DCI6_EMAIL
self.PASSWORD = PLATFORM_DUMMY_DCI6_PASSWORD
else:
self.API_URL = INPUT_URL
self.AUTH_TYPE = 'Internal'
self.HEADERS = HEADERS
self.API_VERSION = 'v3'
self.API_DEFINITION = 'api'
self.HEADERS = HEADERS
self.VERIFY_SSL = verify_ssl
self.VERIFY_SSL = PLATFORM_VERIFY_SSL
def _gen_request_url(self, url):
return f'{self.API_URL}/{self.API_DEFINITION}/{self.API_VERSION}{url}'
@ -61,69 +95,26 @@ class BaseAPI(object):
print(response)
raise sys.exit()
def get(self, url, headers={}, data={}):
return self.call_api(url, headers, data, method='GET')
def post(self, url, headers={}, data={}):
return self.call_api(url, headers, data, method='POST')
class BaseAuthAPI(BaseAPI):
def __init__(self, api_url=None, verify_ssl=True):
"""
Init class for /auth/v4 requests
Args:
api_url (str, optional): url api host. Defaults to None.
If None will use from settings.api.INPUT_HOSTNAME:INPUT_PORT
verify_ssl (bool, optional): Isn't recommended. Defaults to True.
Use only for testing if you don't have root sign SSL cert.
"""
super().__init__(api_url, verify_ssl)
def __init__(self):
super().__init__()
self.API_VERSION = 'v4'
self.API_DEFINITION = 'auth'
def get_auth_token(self, email: str, password: str) -> dict:
"""
Get auth token for authentication
Arg:
email (str): user email
password (str): user password
Returns:
response (dict): {
"confirmed": true,
"expires_at": "date time",
"id": "int",
"token": "str"
}
"""
def get_auth_token(self, email=None, password=None) -> dict:
email = self.EMAIL if not email else email
password = self.PASSWORD if not password else password
return self.call_api(
url='/public/token',
method='POST',
data={'email': email, 'password': password}
)
def get_auth_key(self, token: str, user, auth_type='Internal') -> dict:
"""
Key authentication
Args:
token (str): auth token
user (str or int): user id or email
auth_type (str, optional): May be Public for public auth.
Defaults to 'Internal'.
Returns:
response (dict): {
"id": "int",
"key": "str"
}
"""
def get_auth_key(self, token=None, user=None) -> dict:
headers = {}
if auth_type == 'Public':
user = self.EMAIL if not user else user
if token:
headers = self.make_auth_header(token)
return self.call_api(
url=f'/user/{user}/key',
@ -132,32 +123,22 @@ class BaseAuthAPI(BaseAPI):
)
def make_auth_header(self, token: str) -> dict:
"""
Generate dict for auth header
Args:
token (str): auth token
Returns:
dict: {'x-xsrf-token': token} use it for request headers
"""
return {'x-xsrf-token': token}
def whoami(self, token: str) -> dict:
def whoami(self) -> dict:
return self.call_api(
url='/whoami',
method='GET',
headers=self.make_auth_header(token)
method='GET'
)
class BaseIpAPI(BaseAPI):
def __init__(self, api_url=None, verify_ssl=True):
super().__init__(api_url, verify_ssl)
def __init__(self):
super().__init__()
self.API_DEFINITION = 'ip'
class BaseDnsProxyAPI(BaseAPI):
def __init__(self, api_url=None, verify_ssl=True):
super().__init__(api_url, verify_ssl)
def __init__(self):
super().__init__()
self.API_DEFINITION = 'dnsproxy'

View File

@ -6,8 +6,8 @@ class AuthAPI(BaseAuthAPI):
class BackupAPI(BaseAPI):
def __init__(self, api_url=None, verify_ssl=True):
super().__init__(api_url, verify_ssl)
def __init__(self):
super().__init__()
self.API_VERSION = 'v4'
self.API_DEFINITION = 'backup'
@ -17,20 +17,20 @@ class DnsProxyAPI(BaseDnsProxyAPI):
class EserviceAPI(BaseAPI):
def __init__(self, api_url=None, verify_ssl=True):
super().__init__(api_url, verify_ssl)
def __init__(self):
super().__init__()
self.API_DEFINITION = 'eservice'
class IsoAPI(BaseAPI):
def __init__(self, api_url=None, verify_ssl=True):
super().__init__(api_url, verify_ssl)
def __init__(self):
super().__init__()
self.API_DEFINITION = 'iso'
class IpmiAPI(BaseAPI):
def __init__(self, api_url=None, verify_ssl=True):
super().__init__(api_url, verify_ssl)
def __init__(self):
super().__init__()
self.API_DEFINITION = 'ipmiproxy'
@ -39,13 +39,13 @@ class IpAPI(BaseIpAPI):
class ReportAPI(BaseAPI):
def __init__(self, api_url=None, verify_ssl=True):
super().__init__(api_url, verify_ssl)
def __init__(self):
super().__init__()
self.API_VERSION = 'v4'
self.API_DEFINITION = 'report'
class UpdaterAPI(BaseAPI):
def __init__(self, api_url=None, verify_ssl=True):
super().__init__(api_url, verify_ssl)
def __init__(self):
super().__init__()
self.API_DEFINITION = 'updater'

View File

@ -14,6 +14,6 @@ class IpAPI(BaseIpAPI):
class VmAPI(BaseAPI):
def __init__(self, api_url=None, verify_ssl=True):
super().__init__(api_url, verify_ssl)
def __init__(self):
super().__init__()
self.API_DEFINITION = 'vm'

View File

@ -1,9 +1,11 @@
import click
from mgrctl.db.vm6.databases import isp_database
from mgrctl.db.vm6.models import AuthUser
from mgrctl.apps.vm6.auth import __version__
from mgrctl.api.vm6 import AuthAPI
from mgrctl.utils.api_users import UserAPI
user_cursor = UserAPI(callback_class=AuthAPI)
@click.group(help='auth cmd for auth in VMmanager 6')
@ -16,16 +18,79 @@ def cli():
pass
@cli.command(help='show all users and their roles on platform (DEMO EXAMPLE)')
def users():
# check and init connection to db:
isp_database.connect()
# get all fields from auth_user table
# SELECT * FROM auth_user;
all_users = AuthUser.select()
# Iterate fields and print to console users' email and role
for user in all_users:
result = f'{user.email} | {user.roles[0]}'
click.echo(result)
# Close connection
isp_database.close()
@cli.group(help='Manage users')
def user():
pass
@user.command(help='List users')
@click.option(
'--all',
is_flag=True,
required=False,
help='Show all users'
)
@click.option(
'--admins',
is_flag=True,
required=False,
help='Show all active admins',
)
def ls(all, admins):
if all:
user_cursor.echo_users(role='all')
elif admins:
user_cursor.echo_users(role='admin')
else:
user_cursor.echo_users(role='all')
@user.command(help='Generate access key and return auth link(s)')
@click.option(
'--id',
required=False,
type=int,
help='User id'
)
@click.option(
'--count',
required=False,
type=int,
help='Number of access keys generated',
)
@click.option(
'--random',
is_flag=True,
required=False,
help='Interactive mode, ignores other keys'
)
@click.option(
'--interactive',
is_flag=True,
required=False,
help='Interactive mode, ignores other keys'
)
def access(id, count, interactive, random):
if id and not count:
keys = user_cursor.get_access_keys(user=id, count=1)
links = user_cursor.gen_access_links(keys)
user_cursor.echo_access_links(links)
elif id and count:
keys = user_cursor.get_access_keys(user=id, count=count)
links = user_cursor.gen_access_links(keys)
user_cursor.echo_access_links(links)
elif interactive:
pass
elif random:
admin = user_cursor.get_first_random_admin()
keys = user_cursor.get_access_keys(user=admin.get('id', 3), count=1)
links = user_cursor.gen_access_links(keys)
user_cursor.echo_access_links(links)
else:
pass
@user.command(help='Generate API token for mgrctl user')
def token():
token = user_cursor.gen_api_token()
user_cursor.echo_api_token(token)

View File

@ -1,4 +1,8 @@
from mgrctl.settings.platform import PLATFORM_TYPE
from requests.packages import urllib3
from mgrctl.settings.platform import (
PLATFORM_TYPE,
PLATFORM_VERIFY_SSL_WARNING
)
# Name of nginx container:
INPUT_HOSTNAME = 'input' if PLATFORM_TYPE == 'vm' else 'dci_input_1'
@ -6,5 +10,16 @@ INPUT_HOSTNAME = 'input' if PLATFORM_TYPE == 'vm' else 'dci_input_1'
# Port that nginx container is listening:
INPUT_PORT = '1500'
# Internal API url:
INPUT_URL = f'http://{INPUT_HOSTNAME}:{INPUT_PORT}'
# Headers for internal auth:
HEADERS = {"Internal-Auth": "on", "Accept": "application/json"}
# Suppress warning from urllib3:
if not PLATFORM_VERIFY_SSL_WARNING:
# ! This is not recommended,
# ! the user will not see a message about an untrusted SSL connection
urllib3.disable_warnings(
category=urllib3.exceptions.InsecureRequestWarning
)

View File

@ -4,6 +4,9 @@ from mgrctl.utils.helpers import parse_json_file
PLATFORM_TYPE = env.str('PLATFORM_TYPE', 'vm')
PLATFORM_VERIFY_SSL = env.bool('PLATFORM_VERIFY_SSL', True)
PLATFORM_VERIFY_SSL_WARNING = env.bool('PLATFORM_VERIFY_SSL_WARNING', True)
PLATFORM_CONFIG = env.str(
'PLATFORM_CONFIG',
f'/opt/ispsystem/{PLATFORM_TYPE}/config.json'
@ -11,7 +14,20 @@ PLATFORM_CONFIG = env.str(
PLATFORM_CONFIG = parse_json_file(PLATFORM_CONFIG)
PLATFORM_URL = env.url(
PLATFORM_URL = env.str(
'PLATFORM_URL',
f"https://{PLATFORM_CONFIG.get('DomainName' ,'replace.me')}"
)
# Development mode:
PLATFORM_DUMMY = env.bool('PLATFORM_DUMMY', False)
PLATFORM_DUMMY_VM6_API_URL = env.str('PLATFORM_DUMMY_VM6_API_URL', '')
PLATFORM_DUMMY_VM6_EMAIL = env.str('PLATFORM_DUMMY_VM6_EMAIL', '')
PLATFORM_DUMMY_VM6_PASSWORD = env.str('PLATFORM_DUMMY_VM6_PASSWORD', '')
PLATFORM_DUMMY_VM6_TOKEN = env.str('PLATFORM_DUMMY_VM6_TOKEN', '')
PLATFORM_DUMMY_DCI6_API_URL = env.str('PLATFORM_DUMMY_DCI6_API_URL', '')
PLATFORM_DUMMY_DCI6_EMAIL = env.str('PLATFORM_DUMMY_DCI6_EMAIL', '')
PLATFORM_DUMMY_DCI6_PASSWORD = env.str('PLATFORM_DUMMY_DCI6_PASSWORD', '')
PLATFORM_DUMMY_DCI6_TOKEN = env.str('PLATFORM_DUMMY_DCI6_TOKEN', '')

74
mgrctl/utils/api_users.py Normal file
View File

@ -0,0 +1,74 @@
import click
from tabulate import tabulate
from mgrctl.settings.platform import PLATFORM_URL
class UserAPI(object):
def __init__(self, callback_class):
"""Announces required parameters"""
self.callback_class = callback_class
self.callback = callback_class()
def get_users(self, role: str) -> dict:
data = {}
if role == 'admin':
data = {"where": "((roles+CP+'%@admin%')+AND+(state+EQ+'active'))"}
return self.callback.call_api(
url='/user',
method='GET',
data=data
)
def _format_users(self, users: dict) -> list:
output = []
for user in users.get('list', []):
output.append({
'id': user.get('id', ''),
'email': user.get('email', ''),
'roles': user.get('roles', []),
'state': user.get('state', '')
})
return output
def get_first_random_admin(self):
users = self.get_users(role='admin')
admin = {}
for user in users.get('list', []):
if '@admin' in admin.get('roles', []):
admin = user
break
return admin
def echo_users(self, role: str) -> None:
users = self.get_users(role)
output = self._format_users(users)
click.echo(tabulate(output, headers='keys'))
def get_access_keys(self, user, count=1):
keys = []
while count >= 1:
count -= 1
key = self.callback.get_auth_key(user=user)
check = key.get('key', '')
if check:
keys.append(key)
return keys
def gen_access_links(self, keys: list) -> list:
links = []
for key in keys:
_id = key.get('id', '')
link = f"{PLATFORM_URL}/auth/key/{key.get('key', '')}"
links.append({'id': _id, 'link': link})
return links
def echo_access_links(self, links: list) -> None:
click.echo(tabulate(links, headers='keys'))
def gen_api_token(self, email=None, password=None):
token = self.callback.get_auth_token(email, password)
return token
def echo_api_token(self, token: dict) -> None:
click.echo(tabulate([token], headers='keys'))