From f17d3ee336efc022dff9581325f548f6a0c77f70 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Thu, 11 Aug 2022 15:28:39 +0300 Subject: [PATCH 001/364] feat: jans-cli TUI initial commit --- jans-cli-tui/cli/config_cli.py | 2151 ++++ jans-cli-tui/cli/jca.yaml | 7015 +++++++++++ jans-cli-tui/cli/pylib/__init__.py | 0 jans-cli-tui/cli/pylib/tabulate/LICENSE | 20 + jans-cli-tui/cli/pylib/tabulate/README | 1 + jans-cli-tui/cli/pylib/tabulate/README.md | 747 ++ jans-cli-tui/cli/pylib/tabulate/__init__.py | 0 jans-cli-tui/cli/pylib/tabulate/tabulate.py | 1792 +++ jans-cli-tui/cli/swagger_yaml.json | 10406 ++++++++++++++++ jans-cli-tui/cli_style.py | 21 + jans-cli-tui/jans-cli-tui.py | 356 + jans-cli-tui/models/oauth.py | 285 + jans-cli-tui/static.py | 6 + .../wui_components/edit_client_dialog.py | 94 + .../wui_components/edit_scope_dialog.py | 94 + .../wui_components/jans_cli_dialog.py | 35 + jans-cli-tui/wui_components/jans_dialog.py | 46 + .../wui_components/jans_dialog_with_nav.py | 57 + jans-cli-tui/wui_components/jans_nav_bar.py | 63 + .../wui_components/jans_side_nav_bar.py | 87 + .../wui_components/jans_vetrical_nav.py | 197 + 21 files changed, 23473 insertions(+) create mode 100755 jans-cli-tui/cli/config_cli.py create mode 100644 jans-cli-tui/cli/jca.yaml create mode 100644 jans-cli-tui/cli/pylib/__init__.py create mode 100644 jans-cli-tui/cli/pylib/tabulate/LICENSE create mode 100644 jans-cli-tui/cli/pylib/tabulate/README create mode 100644 jans-cli-tui/cli/pylib/tabulate/README.md create mode 100644 jans-cli-tui/cli/pylib/tabulate/__init__.py create mode 100644 jans-cli-tui/cli/pylib/tabulate/tabulate.py create mode 100644 jans-cli-tui/cli/swagger_yaml.json create mode 100644 jans-cli-tui/cli_style.py create mode 100755 jans-cli-tui/jans-cli-tui.py create mode 100644 jans-cli-tui/models/oauth.py create mode 100644 jans-cli-tui/static.py create mode 100644 jans-cli-tui/wui_components/edit_client_dialog.py create mode 100644 jans-cli-tui/wui_components/edit_scope_dialog.py create mode 100644 jans-cli-tui/wui_components/jans_cli_dialog.py create mode 100644 jans-cli-tui/wui_components/jans_dialog.py create mode 100644 jans-cli-tui/wui_components/jans_dialog_with_nav.py create mode 100644 jans-cli-tui/wui_components/jans_nav_bar.py create mode 100644 jans-cli-tui/wui_components/jans_side_nav_bar.py create mode 100644 jans-cli-tui/wui_components/jans_vetrical_nav.py diff --git a/jans-cli-tui/cli/config_cli.py b/jans-cli-tui/cli/config_cli.py new file mode 100755 index 00000000000..802367de006 --- /dev/null +++ b/jans-cli-tui/cli/config_cli.py @@ -0,0 +1,2151 @@ +#!/usr/bin/env python3 + +import sys +import os +import json +import re +import urllib3 +import configparser +import readline +import argparse +import random +import datetime +import ruamel.yaml +import code +import traceback +import ast +import base64 +import requests +import html +import glob +import logging +import http.client + +from pathlib import Path +from types import SimpleNamespace +from urllib.parse import urlencode +from collections import OrderedDict +from urllib.parse import urljoin +from http.client import HTTPConnection +from pygments import highlight, lexers, formatters + +home_dir = Path.home() +config_dir = home_dir.joinpath('.config') +config_dir.mkdir(parents=True, exist_ok=True) +config_ini_fn = config_dir.joinpath('jans-cli.ini') +cur_dir = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(cur_dir) + +from pylib.tabulate.tabulate import tabulate +try: + import jwt +except ModuleNotFoundError: + from pylib import jwt + +tabulate_endpoints = { + 'jca.get-config-scripts': ['scriptType', 'name', 'enabled', 'inum'], + 'jca.get-user': ['inum', 'userId', 'mail','sn', 'givenName', 'jansStatus'], + 'jca.get-attributes': ['inum', 'name', 'displayName', 'status', 'dataType', 'claimName'], + 'jca.get-oauth-openid-clients': ['inum', 'displayName', 'clientName', 'applicationType'], + 'jca.get-oauth-scopes': ['dn', 'id', 'scopeType'], + 'scim.get-users': ['id', 'userName', 'displayName', 'active'] +} + +tabular_dataset = {'scim.get-users': 'Resources'} + +my_op_mode = 'scim' if 'scim' in os.path.basename(sys.argv[0]) else 'jca' +plugins = [] + +warning_color = 214 +error_color = 196 +success_color = 10 +bold_color = 15 +grey_color = 242 + + +def clear(): + if not debug: + os.system('clear') + +urllib3.disable_warnings() +config = configparser.ConfigParser() + +host = os.environ.get('jans_host') +client_id = os.environ.get(my_op_mode + 'jca_client_id') +client_secret = os.environ.get(my_op_mode + 'jca_client_secret') +access_token = None +debug = os.environ.get('jans_client_debug') +log_dir = os.environ.get('cli_log_dir', cur_dir) + +def encode_decode(s, decode=False): + cmd = '/opt/jans/bin/encode.py ' + if decode: + cmd += '-D ' + result = os.popen(cmd + s + ' 2>/dev/null').read() + return result.strip() + + +##################### arguments ##################### + +# load yaml file + +my_op_mode + +debug_json = 'swagger_yaml.json' +if os.path.exists(debug_json): + with open(debug_json) as f: + cfg_yml = json.load(f, object_pairs_hook=OrderedDict) +else: + with open(os.path.join(cur_dir, my_op_mode+'.yaml')) as f: + cfg_yml = ruamel.yaml.load(f.read().replace('\t', ''), ruamel.yaml.RoundTripLoader) + if os.environ.get('dump_yaml'): + with open(debug_json, 'w') as w: + json.dump(cfg_yml, w, indent=2) + +op_list = [] + +name_regex = re.compile('[^a-zA-Z0-9]') + +def get_named_tag(tag): + return name_regex.sub('', tag.title()) + +for path in cfg_yml['paths']: + for method in cfg_yml['paths'][path]: + if isinstance(cfg_yml['paths'][path][method], dict): + for tag_ in cfg_yml['paths'][path][method].get('tags', []): + tag = get_named_tag(tag_) + if not tag in op_list: + op_list.append(tag) + +for yml_fn in glob.glob(os.path.join(cur_dir, '*.yaml')): + fname, fext = os.path.splitext(os.path.basename(yml_fn)) + op_list.append(fname) + +op_list.sort() + +parser = argparse.ArgumentParser() +parser.add_argument("--host", help="Hostname of server") +parser.add_argument("--client-id", help="Jans Config Api Client ID") +parser.add_argument("--client-secret", "--client_secret", help="Jans Config Api Client ID secret") +parser.add_argument("--access-token", help="JWT access token or path to file containing JWT access token") +parser.add_argument("--plugins", help="Available plugins separated by comma") +parser.add_argument("-debug", help="Run in debug mode", action='store_true') +parser.add_argument("--debug-log-file", default='swagger.log', help="Log file name when run in debug mode") +parser.add_argument("--operation-id", help="Operation ID to be done") +parser.add_argument("--url-suffix", help="Argument to be added api endpoint url. For example inum:2B29") +parser.add_argument("--info", choices=op_list, help="Help for operation") +parser.add_argument("--op-mode", choices=['get', 'post', 'put', 'patch', 'delete'], default='get', + help="Operation mode to be done") +parser.add_argument("--endpoint-args", + help="Arguments to pass endpoint separated by comma. For example limit:5,status:INACTIVE") +parser.add_argument("--schema", help="Get sample json schema") + +parser.add_argument("-CC", "--config-api-mtls-client-cert", help="Path to SSL Certificate file") +parser.add_argument("-CK", "--config-api-mtls-client-key", help="Path to SSL Key file") +parser.add_argument("--key-password", help="Password for SSL Key file") +parser.add_argument("-noverify", help="Ignore verifying the SSL certificate", action='store_true', default=True) + +parser.add_argument("-use-test-client", help="Use test client without device authorization", action='store_true') + + +parser.add_argument("--patch-add", help="Colon delimited key:value pair for add patch operation. For example loggingLevel:DEBUG") +parser.add_argument("--patch-replace", help="Colon delimited key:value pair for replace patch operation. For example loggingLevel:DEBUG") +parser.add_argument("--patch-remove", help="Key for remove patch operation. For example imgLocation") +parser.add_argument("-no-color", help="Do not colorize json dumps", action='store_true') +parser.add_argument("--log-dir", help="Log directory", default=log_dir) + +parser.add_argument("--data", help="Path to json data file") +args = parser.parse_args() + + +################## end of arguments ################# + +test_client = args.use_test_client + + +if args.plugins: + for plugin in args.plugins.split(','): + plugins.append(plugin.strip()) + + +if not(host and (client_id and client_secret or access_token)): + host = args.host + client_id = args.client_id + client_secret = args.client_secret + debug = args.debug + log_dir = args.log_dir + + access_token = args.access_token + if access_token and os.path.isfile(access_token): + with open(access_token) as f: + access_token = f.read() + + +if not(host and (client_id and client_secret or access_token)): + + if config_ini_fn.exists(): + config.read_string(config_ini_fn.read_text()) + host = config['DEFAULT']['jans_host'] + + if 'jca_test_client_id' in config['DEFAULT'] and test_client: + client_id = config['DEFAULT']['jca_test_client_id'] + secret_key_str = 'jca_test_client_secret' + else: + client_id = config['DEFAULT']['jca_client_id'] + secret_key_str = 'jca_client_secret' + + secret_enc_key_str = secret_key_str + '_enc' + if config['DEFAULT'].get(secret_key_str): + client_secret = config['DEFAULT'][secret_key_str] + elif config['DEFAULT'].get(secret_enc_key_str): + client_secret_enc = config['DEFAULT'][secret_enc_key_str] + client_secret = encode_decode(client_secret_enc, decode=True) + + debug = config['DEFAULT'].get('debug') + log_dir = config['DEFAULT'].get('log_dir', log_dir) + + else: + config['DEFAULT'] = {'jans_host': 'jans server hostname,e.g, jans.foo.net', + 'jca_client_id': 'your jans config api client id', + 'jca_client_secret': 'client secret for your jans config api client', + 'scim_client_id': 'your jans scim client id', + 'scim_client_secret': 'client secret for your jans scim client'} + + +def get_bool(val): + if str(val).lower() in ('yes', 'true', '1', 'on'): + return True + return False + +def write_config(): + with open(config_ini_fn, 'w') as w: + config.write(w) + +debug = get_bool(debug) + + +class Menu(object): + + def __init__(self, name, method='', info={}, path=''): + self.name = name + self.display_name = name + self.method = method + self.info = info + self.path = path + self.children = [] + self.parent = None + self.ignore = False + + def __iter__(self): + self.current_index = 0 + return self + + def __repr__(self): + return self.display_name + self.__print_child(self) + + def tree(self): + print(self.name) + self.__print_child(self) + + def __get_parent_number(self, child): + n = 0 + while True: + if not child.parent: + break + n += 1 + child = child.parent + + return n + + def __print_child(self, menu): + if menu.children: + for child in menu.children: + print(' ' * self.__get_parent_number(child) * 2, child) + self.__print_child(child) + + def add_child(self, node): + assert isinstance(node, Menu) + node.parent = self + self.children.append(node) + + def get_child(self, n): + if len(self.children) > n: + return self.children[n] + + def __next__(self): + if self.current_index < len(self.children): + retVal = self.children[self.current_index] + self.current_index += 1 + return retVal + + else: + raise StopIteration + + def __contains__(self, child): + for child_ in self.children: + if child_.name == child: + return True + return False + + +class JCA_CLI: + + def __init__(self, host, client_id, client_secret, access_token, test_client=False, wrapped=False): + self.host = self.idp_host = host + self.client_id = client_id + self.client_secret = client_secret + self.use_test_client = test_client + self.getCredintials() + self.wrapped = wrapped + self.access_token = access_token or config['DEFAULT'].get('access_token') + self.jwt_validation_url = 'https://{}/jans-config-api/api/v1/acrs'.format(self.idp_host) + self.set_user() + self.plugins() + + if not self.access_token and config['DEFAULT'].get('access_token_enc'): + self.access_token = encode_decode(config['DEFAULT']['access_token_enc'], decode=True) + + if my_op_mode == 'scim': + self.host += '/jans-scim/restv1/v2' + + self.set_logging() + self.ssl_settings() + self.make_menu() + self.current_menu = self.menu + self.enums() + + def getCredintials(self): + if self.host == '' or self.client_id == '' or self.client_secret == '' : + if config_ini_fn.exists(): + config.read_string(config_ini_fn.read_text()) + host_data = config['DEFAULT']['jans_host'] + + if 'jca_test_client_id' in config['DEFAULT'] and test_client: + client_id_data = config['DEFAULT']['jca_test_client_id'] + secret_key_str = 'jca_test_client_secret' + else: + client_id_data = config['DEFAULT']['jca_client_id'] + secret_key_str = 'jca_client_secret' + + secret_enc_key_str = secret_key_str + '_enc' + if config['DEFAULT'].get(secret_key_str): + client_secret_data = config['DEFAULT'][secret_key_str] + elif config['DEFAULT'].get(secret_enc_key_str): + client_secret_enc = config['DEFAULT'][secret_enc_key_str] + client_secret_data = encode_decode(client_secret_enc, decode=True) + + self.host = self.idp_host=host_data.replace("'","") + self.client_id = client_id_data.replace("'","") + self.client_secret = client_secret_data.replace("'","") + + def set_logging(self): + if debug: + self.cli_logger = logging.getLogger("urllib3") + self.cli_logger.setLevel(logging.DEBUG) + self.cli_logger.propagate = True + HTTPConnection.debuglevel = 1 + file_handler = logging.FileHandler(os.path.join(log_dir, 'cli_debug.log')) + file_handler.setLevel(logging.DEBUG) + file_handler.setFormatter(logging.Formatter("%(asctime)s [%(levelname)-5.5s] %(message)s")) + self.cli_logger.addHandler(file_handler) + def print_to_log(*args): + self.cli_logger.debug(" ".join(args)) + http.client.print = print_to_log + + + def log_response(self, response): + if debug: + self.cli_logger.debug('requests response status: %s', str(response.status_code)) + self.cli_logger.debug('requests response headers: %s', str(response.headers)) + self.cli_logger.debug('requests response text: %s', str(response.text)) + + def enums(self): + self.enum_dict = { + "CustomAttribute": { + "properties.name": { + "f": "get_attrib_list" + } + } + } + + def set_user(self): + self.auth_username = None + self.auth_password = None + self.askuser = get_bool(config['DEFAULT'].get('askuser')) + + if self.askuser: + if args.username: + self.auth_username = args.username + if args.password: + self.auth_password = args.password + elif args.j: + if os.path.isfile(args.j): + with open(args.j) as reader: + self.auth_password = reader.read() + else: + print(args.j, "does not exist. Exiting ...") + sys.exit() + if not (self.auth_username and self.auth_password): + print("I need username and password. Exiting ...") + sys.exit() + + def plugins(self): + for plugin_s in config['DEFAULT'].get(my_op_mode + '_plugins', '').split(','): + plugin = plugin_s.strip() + if plugin: + plugins.append(plugin) + + def ssl_settings(self): + if args.noverify: + self.verify_ssl = False + else: + self.verify_ssl = True + self.mtls_client_cert = None + if args.config_api_mtls_client_cert and args.config_api_mtls_client_key: + self.mtls_client_cert = (args.config_api_mtls_client_cert, args.config_api_mtls_client_key) + + def drop_to_shell(self, mylocals): + locals_ = locals() + locals_.update(mylocals) + code.interact(local=locals_) + sys.exit() + + def get_request_header(self, headers={}, access_token=None): + if not access_token: + access_token = self.access_token + + ret_val = {'Authorization': 'Bearer {}'.format(access_token)} + ret_val.update(headers) + return ret_val + + + def check_connection(self): + url = 'https://{}/jans-auth/restv1/token'.format(self.idp_host) + response = requests.post( + url=url, + auth=(self.client_id, self.client_secret), + data={"grant_type": "client_credentials"}, + verify=self.verify_ssl, + cert=self.mtls_client_cert + ) + + self.log_response(response) + if response.status_code != 200: + raise ValueError( + self.colored_text("Unable to connect jans-auth server:\n {}".format(response.text), error_color)) + + if not self.use_test_client and self.access_token: + response = requests.get( + url = self.jwt_validation_url, + headers=self.get_request_header({'Accept': 'application/json'}), + verify=self.verify_ssl, + cert=self.mtls_client_cert + ) + + if not response.status_code == 200: + self.access_token = None + + + def check_access_token(self): + + if not self.access_token : + print(self.colored_text("Access token was not found.", warning_color)) + return + + try: + jwt.decode(self.access_token, + options={ + 'verify_signature': False, + 'verify_exp': True, + 'verify_aud': False + } + ) + except Exception as e: + print(self.colored_text("Unable to validate access token: {}".format(e), error_color)) + self.access_token = None + + + def validate_date_time(self, date_str): + try: + datetime.datetime.fromisoformat(date_str) + return True + except Exception as e: + self.log_response('invalid date-time format: %s'.format(str(e))) + return False + + + def make_menu(self): + + menu_groups = [] + + def get_sep_pos(s): + for i, c in enumerate(s): + if c in ('-', '–'): + return i + return -1 + + def get_group_obj(mname): + for grp in menu_groups: + if grp.mname == mname: + return grp + + + for tag in cfg_yml['tags']: + tname = tag['name'].strip() + if tname == 'developers': + continue + n = get_sep_pos(tname) + mname = tname[:n].strip() if n > -1 else tname + grp = get_group_obj(mname) + if not grp: + grp = SimpleNamespace() + grp.tag = None if n > -1 else tname + grp.mname = mname + grp.submenu = [] + menu_groups.append(grp) + + if n > -1: + sname = tname[n+1:].strip() + sub = SimpleNamespace() + sub.tag = tname + sub.mname = sname + grp.submenu.append(sub) + + + def get_methods_of_tag(tag): + methods = [] + if tag: + for path_name in cfg_yml['paths']: + path = cfg_yml['paths'][path_name] + for method_name in path: + method = path[method_name] + if 'tags' in method and tag in method['tags'] and 'operationId' in method: + if method.get('x-cli-plugin') and method['x-cli-plugin'] not in plugins: + continue + method['__method_name__'] = method_name + method['__path_name__'] = path_name + methods.append(method) + + return methods + + menu = Menu('Main Menu') + + + for grp in menu_groups: + methods = get_methods_of_tag(grp.tag) + m = Menu(name=grp.mname) + m.display_name = m.name + ' ˅' + menu.add_child(m) + + for method in methods: + for tag in method['tags']: + menu_name = method.get('summary') or method.get('description') + sm = Menu( + name=menu_name.strip('.'), + method=method['__method_name__'], + info=method, + path=method['__path_name__'], + ) + m.add_child(sm) + + if grp.submenu: + m.display_name = m.name + ' ˅' + for sub in grp.submenu: + methods = get_methods_of_tag(sub.tag) + if not methods: + continue + smenu = Menu(name=sub.mname) + smenu.display_name = smenu.name + ' ˅' + m.add_child(smenu) + + for method in methods: + for tag in method['tags']: + + sub_menu_name = method.get('summary') or method.get('description') + ssm = Menu( + name=sub_menu_name.strip('.'), + method=method['__method_name__'], + info=method, + path=method['__path_name__'], + ) + smenu.add_child(ssm) + + self.menu = menu + + + def get_scoped_access_token(self, scope): + + if not self.wrapped: + scope_text = " for scope {}\n".format(scope) if scope else '' + sys.stderr.write("Getting access token{}".format(scope_text)) + + url = 'https://{}/jans-auth/restv1/token'.format(self.host) + + if self.askuser: + post_params = {"grant_type": "password", "scope": scope, "username": self.auth_username, + "password": self.auth_password} + else: + post_params = {"grant_type": "client_credentials", "scope": scope} + + response = requests.post( + url, + auth=(self.use_test_client, self.client_secret), + data=post_params, + verify=self.verify_ssl, + cert=self.mtls_client_cert + ) + self.log_response(response) + try: + result = response.json() + if 'access_token' in result: + self.access_token = result['access_token'] + else: + sys.stderr.write("Error while getting access token") + sys.stderr.write(result) + sys.stderr.write('\n') + except Exception as e: + print("Error while getting access token") + sys.stderr.write(response.text) + sys.stderr.write(str(e)) + sys.stderr.write('\n') + + def get_device_authorization (self): + response = requests.post( + url='https://{}/jans-auth/restv1/device_authorization'.format(self.host), + auth=(self.client_id, self.client_secret), + data={'client_id': self.client_id, 'scope': 'openid+profile+email+offline_access'}, + verify=self.verify_ssl, + cert=self.mtls_client_cert + ) + self.log_response(response) + if response.status_code != 200: + raise ValueError( + self.colored_text("Unable to get device authorization user code: {}".format(response.reason), error_color)) + + return response.json() + + + def get_jwt_access_token(self): + + + """ + STEP 1: Get device verification code + This fucntion requests user code from jans-auth, print result and + waits untill verification done. + """ + + response = requests.post( + url='https://{}/jans-auth/restv1/device_authorization'.format(self.host), + auth=(self.client_id, self.client_secret), + data={'client_id': self.client_id, 'scope': 'openid+profile+email+offline_access'}, + verify=self.verify_ssl, + cert=self.mtls_client_cert + ) + self.log_response(response) + if response.status_code != 200: + raise ValueError( + self.colored_text("Unable to get device authorization user code: {}".format(response.reason), error_color)) + + result = response.json() + + if 'verification_uri' in result and 'user_code' in result: + + print("Please visit verification url {} and enter user code {} in {} secods".format( + self.colored_text(result['verification_uri'], success_color), + self.colored_text(result['user_code'], bold_color), + result['expires_in'] + ) + ) + + input(self.colored_text("Please press «Enter» when ready", warning_color)) + + else: + raise ValueError(self.colored_text("Unable to get device authorization user code")) + + + """ + STEP 2: Get access token for retreiving user info + After device code was verified, we use it to retreive refresh token + """ + response = requests.post( + url='https://{}/jans-auth/restv1/token'.format(self.host), + auth=(self.client_id, self.client_secret), + data=[ + ('client_id',self.client_id), + ('scope','openid+profile+email+offline_access'), + ('grant_type', 'urn:ietf:params:oauth:grant-type:device_code'), + ('grant_type', 'refresh_token'), + ('device_code',result['device_code']) + ], + verify=self.verify_ssl, + cert=self.mtls_client_cert + ) + self.log_response(response) + if response.status_code != 200: + raise ValueError( + self.colored_text("Unable to get access token")) + + result = response.json() + + headers_basic_auth = self.get_request_header(access_token=result['access_token']) + + """ + STEP 3: Get user info + refresh token is used for retreiving user information to identify user roles + """ + response = requests.post( + url='https://{}/jans-auth/restv1/userinfo'.format(self.host), + headers=headers_basic_auth, + data={'access_token': result['access_token']}, + verify=self.verify_ssl, + cert=self.mtls_client_cert + ) + self.log_response(response) + if response.status_code != 200: + raise ValueError( + self.colored_text("Unable to get access token")) + + result = response.text + + + """ + STEP 4: Get access token for config-api endpoints + Use client creditentials to retreive access token for client endpoints. + Since introception script will be executed, access token will have permissions with all scopes + """ + response = requests.post( + url='https://{}/jans-auth/restv1/token'.format(self.host), + headers=headers_basic_auth, + data={'grant_type': 'client_credentials', 'scope': 'openid', 'ujwt': result}, + verify=self.verify_ssl, + cert=self.mtls_client_cert + ) + self.log_response(response) + if response.status_code != 200: + raise ValueError( + self.colored_text("Unable to get access token")) + + result = response.json() + + self.access_token = result['access_token'] + access_token_enc = encode_decode(self.access_token) + config['DEFAULT']['access_token_enc'] = access_token_enc + write_config() + + + def get_access_token(self, scope): + if self.use_test_client: + self.get_scoped_access_token(scope) + elif not self.access_token: + self.check_access_token() + self.get_jwt_access_token() + + def print_exception(self, e): + error_printed = False + if hasattr(e, 'body'): + try: + jsdata = json.loads(e.body.decode()) + print(self.colored_text(e.body.decode(), error_color)) + error_printed = True + except: + pass + if not error_printed: + print(self.colored_text("Error retreiving data", warning_color)) + print('\u001b[38;5;196m') + if isinstance(e, str): + print(e) + if hasattr(e, 'reason'): + print(e.reason) + if hasattr(e, 'body'): + print(e.body) + if hasattr(e, 'args'): + print(', '.join(e.args)) + print('\u001b[0m') + + def colored_text(self, text, color=255): + return u"\u001b[38;5;{}m{}\u001b[0m".format(color, text) + + + def guess_bool(self, val): + if val == '_false': + return False + if val == '_true': + return True + + + def check_type(self, val, vtype): + if vtype == 'string' and val: + return str(val) + elif vtype == 'integer': + if isinstance(val, int): + return val + if val.isnumeric(): + return int(val) + elif vtype == 'object': + try: + retVal = json.loads(val) + if isinstance(retVal, dict): + return retVal + except: + pass + elif vtype == 'boolean': + guessed_val = self.guess_bool(val) + if not guessed_val is None: + return guessed_val + + error_text = "Please enter a(n) {} value".format(vtype) + if vtype == 'boolean': + error_text += ': _true, _false' + + raise TypeError(self.colored_text(error_text, warning_color)) + + def get_input(self, values=[], text='Selection', default=None, itype=None, + help_text=None, sitype=None, enforce='__true__', + example=None, spacing=0, iformat=None + ): + if isinstance(default, str): + default = html.escape(default) + + if 'b' in values and 'q' in values and 'x' in values: + greyed_help_list = [ ('b', 'back'), ('q', 'quit'), ('x', 'logout and quit') ] + for k,v in (('w', 'write result'), ('y', 'yes'), ('n', 'no')): + if k in values: + greyed_help_list.insert(1, (k, v)) + grey_help_text = ', '.join(['{}: {}'.format(k,v) for k,v in greyed_help_list]) + print(self.colored_text(grey_help_text, grey_color)) + print() + type_text = '' + if itype: + if itype == 'array': + type_text = "Type: array of {} separated by _,".format(sitype) + if values: + type_text += ' Valid values: {}'.format(', '.join(values)) + elif itype == 'boolean': + type_text = "Type: " + itype + if default is None: + default = False + else: + type_text = "Type: " + itype + if values: + type_text += ', Valid values: {}'.format(self.colored_text(', '.join(values), bold_color)) + + if help_text: + help_text = help_text.strip('.') + '. ' + type_text + else: + help_text = type_text + + if help_text: + print(' ' * spacing, self.colored_text('«{}»'.format(help_text), 244), sep='') + + if example: + if isinstance(example, list): + example_str = ', '.join(example) + else: + example_str = str(example) + print(' ' * spacing, self.colored_text('Example: {}'.format(example_str), 244), sep='') + + if not default is None: + default_text = str(default).lower() if itype == 'boolean' else str(default) + text += ' [' + default_text + ']' + if itype == 'integer': + default = int(default) + + if not text.endswith('?'): + text += ':' + + if itype == 'boolean' and not values: + values = ['_true', '_false'] + + while True: + + selection = input(' ' * spacing + self.colored_text(text, 20) + ' ') + + selection = selection.strip() + if selection.startswith('_file '): + fname = selection.split()[1] + if os.path.isfile(fname): + with open(fname) as f: + selection = f.read().strip() + else: + print(self.colored_text("File {} does not exist".format(fname), warning_color)) + continue + + if itype == 'boolean' and not selection: + return False + + if not selection and default: + return default + + if enforce and not selection: + continue + + if not enforce and not selection: + if itype == 'array': + return [] + return None + + if selection and iformat: + if iformat == 'date-time' and not self.validate_date_time(selection): + print(' ' * spacing, + self.colored_text('Please enter date-time string, i.e. 2001-07-04T12:08:56.235', warning_color), + sep='') + continue + + if 'q' in values and selection == 'q': + print("Quiting...") + sys.exit() + + if 'x' in values and selection == 'x': + print("Logging out...") + if 'access_token_enc' in config['DEFAULT']: + config['DEFAULT'].pop('access_token_enc') + write_config() + print("Quiting...") + sys.exit() + break + + + if itype == 'object' and sitype: + try: + object_ = self.check_type(selection, itype) + except Exception as e: + print(' ' * spacing, e, sep='') + continue + + data_ok = True + for items in object_: + try: + self.check_type(object_[items], sitype) + except Exception as e: + print(' ' * spacing, e, sep='') + data_ok = False + if data_ok: + return object_ + else: + continue + + if itype == 'array' and default and not selection: + return default + + if itype == 'array' and sitype: + if selection == '_null': + selection = [] + data_ok = True + else: + selection = selection.split('_,') + for i, item in enumerate(selection): + data_ok = True + try: + selection[i] = self.check_type(item.strip(), sitype) + if selection[i] == '_null': + selection[i] = None + if values: + if not selection[i] in values: + data_ok = False + print(' ' * spacing, self.colored_text( + "Please enter array of {} separated by _,".format(', '.join(values)), + warning_color), sep='') + break + except TypeError as e: + print(' ' * spacing, e, sep='') + data_ok = False + if data_ok: + break + else: + if not itype is None: + try: + selection = self.check_type(selection, itype) + except TypeError as e: + if enforce: + print(' ' * spacing, e, sep='') + continue + + if values: + if selection in values: + break + elif itype == 'boolean': + if isinstance(selection, bool): + break + else: + continue + else: + print(' ' * spacing, + self.colored_text('Please enter one of {}'.format(', '.join(values)), warning_color), + sep='') + + if not values and not selection and not enforce: + break + + if not values and selection: + break + + if selection == '_null': + selection = None + elif selection == '_q': + selection = 'q' + + return selection + + + def print_underlined(self, text): + print() + print(text) + print('-' * len(text.splitlines()[-1])) + + + def pretty_print(self, data): + pp_string = json.dumps(data, indent=2) + if args.no_color: + print(pp_string) + else: + colorful_json = highlight(pp_string, lexers.JsonLexer(), formatters.TerminalFormatter()) + print(colorful_json) + + def get_url_param(self, url): + if url.endswith('}'): + pname = re.findall('/\{(.*?)\}$', url)[0] + return pname + + def get_endpiont_url_param(self, endpoint): + param = {} + pname = self.get_url_param(endpoint.path) + if pname: + param = {'name': pname, 'description': pname, 'schema': {'type': 'string'}} + + return param + + + def obtain_parameters(self, endpoint, single=False): + parameters = {} + + endpoint_parameters = [] + if 'parameters' in endpoint.info: + endpoint_parameters = endpoint.info['parameters'] + + end_point_param = self.get_endpiont_url_param(endpoint) + if end_point_param and not end_point_param in endpoint_parameters: + endpoint_parameters.insert(0, end_point_param) + + n = 1 if single else len(endpoint_parameters) + + for param in endpoint_parameters[0:n]: + param_name = param['name'] + if param_name not in parameters: + text_ = param['name'] + help_text = param.get('description') or param.get('summary') + enforce = True if param['schema']['type'] == 'integer' or (end_point_param and end_point_param['name'] == param['name']) else False + + parameters[param_name] = self.get_input( + text=text_.strip('.'), + itype=param['schema']['type'], + default=param['schema'].get('default'), + enforce=enforce, + help_text=help_text, + example=param.get('example'), + values=param['schema'].get('enum', []) + ) + + return parameters + + + def get_path_by_id(self, operation_id): + retVal = {} + for path in cfg_yml['paths']: + for method in cfg_yml['paths'][path]: + if 'operationId' in cfg_yml['paths'][path][method] and cfg_yml['paths'][path][method][ + 'operationId'] == operation_id: + retVal = cfg_yml['paths'][path][method].copy() + retVal['__path__'] = path + retVal['__method__'] = method + retVal['__urlsuffix__'] = self.get_url_param(path) + + return retVal + + + def get_scope_for_endpoint(self, endpoint): + scope = [] + for security in endpoint.info.get('security', []): + for stype in security: + scope += security[stype] + + return ' '.join(scope) + + + def tabular_data(self, data, ome): + tab_data = [] + headers = tabulate_endpoints[ome] + for i, entry in enumerate(data): + row_ = [i + 1] + for header in headers: + row_.append(str(entry.get(header, ''))) + tab_data.append(row_) + + print(tabulate(tab_data, ['#']+headers, tablefmt="grid")) + + + def get_requests(self, endpoint, params={}): + if not self.wrapped: + sys.stderr.write("Please wait while retreiving data ...\n") + + security = self.get_scope_for_endpoint(endpoint) + self.get_access_token(security) + + url_param_name = self.get_url_param(endpoint.path) + + url = 'https://{}{}'.format(self.host, endpoint.path) + if params and url_param_name in params: + url = url.format(**{url_param_name: params.pop(url_param_name)}) + + if params: + url += '?' + urlencode(params) + + response = requests.get( + url = url, + headers=self.get_request_header({'Accept': 'application/json'}), + params=params, + verify=self.verify_ssl, + cert=self.mtls_client_cert + ) + self.log_response(response) + if response.status_code == 404: + print(self.colored_text("Server returned 404", error_color)) + print(self.colored_text(response.text, error_color)) + return None + + try: + return response.json() + print(response.status_code) + except Exception as e: + print("An error ocurred while retreiving data") + self.print_exception(e) + + def process_get(self, endpoint, return_value=False, parameters=None, noprompt=False, update=False): + clear() + if not return_value: + title = endpoint.name + if endpoint.name != endpoint.info['description'].strip('.'): + title += '\n' + endpoint.info['description'] + + self.print_underlined(title) + + if not parameters and not noprompt: + parameters = self.obtain_parameters(endpoint, single=return_value) + + for param in parameters.copy(): + if not parameters[param]: + del parameters[param] + + if parameters and not return_value: + print("Calling Api with parameters:", parameters) + + data = self.get_requests(endpoint, parameters) + + if return_value: + return data + + selections = ['q', 'x', 'b'] + item_counters = [] + tabulated = False + + if data: + try: + if 'response' in data: + data = data['response'] + except: + pass + + op_mode_endpoint = my_op_mode + '.' + endpoint.info['operationId'] + import copy + if op_mode_endpoint in tabulate_endpoints: + try: + data_ext = copy.deepcopy(data) + if endpoint.info['operationId'] == 'get-user': + for entry in data_ext: + if entry.get('customAttributes'): + for attrib in entry['customAttributes']: + if attrib['name'] == 'mail': + entry['mail'] = ', '.join(attrib['values']) + elif attrib['name'] in tabulate_endpoints[op_mode_endpoint]: + entry[attrib['name']] = attrib['values'][0] + + tab_data = data_ext + if op_mode_endpoint in tabular_dataset: + tab_data = data_ext[tabular_dataset[op_mode_endpoint]] + self.tabular_data(tab_data, op_mode_endpoint) + item_counters = [str(i + 1) for i in range(len(data))] + tabulated = True + except: + self.pretty_print(data) + else: + self.pretty_print(data) + + if update: + return item_counters, data + + selections += item_counters + while True: + selection = self.get_input(selections) + if selection == 'b': + self.display_menu(endpoint.parent) + break + elif selection == 'w': + fn = input('File name: ') + try: + with open(fn, 'w') as w: + json.dump(data, w, indent=2) + print("Output was written to", fn) + except Exception as e: + print("An error ocurred while saving data") + self.print_exception(e) + elif selection in item_counters: + self.pretty_print(data[int(selection) - 1]) + + def get_schema_from_reference(self, ref): + schema_path_list = ref.strip('/#').split('/') + schema = cfg_yml[schema_path_list[0]] + + schema_ = schema.copy() + + for p in schema_path_list[1:]: + schema_ = schema_[p] + + if 'allOf' in schema_: + all_schema = OrderedDict() + all_schema['required'] = [] + + all_schema['properties'] = OrderedDict() + for sch in schema_['allOf']: + if '$ref' in sch: + all_schema.update(self.get_schema_from_reference(sch['$ref'])) + elif 'properties' in sch: + for sprop in sch['properties']: + all_schema['properties'][sprop] = sch['properties'][sprop] + all_schema['required'] += sch.get('required', []) + + schema_ = all_schema + + for key_ in schema_.get('properties', []): + if '$ref' in schema_['properties'][key_]: + schema_['properties'][key_] = self.get_schema_from_reference(schema_['properties'][key_]['$ref']) + elif schema_['properties'][key_].get('type') == 'array' and '$ref' in schema_['properties'][key_]['items']: + ref_path = schema_['properties'][key_]['items'].pop('$ref') + ref_schema = self.get_schema_from_reference(ref_path) + schema_['properties'][key_]['properties'] = ref_schema['properties'] + schema_['properties'][key_]['title'] = ref_schema['title'] + schema_['properties'][key_]['description'] = ref_schema.get('description', '') + schema_['properties'][key_]['__schema_name__'] = ref_schema['__schema_name__'] + + if not 'title' in schema_: + schema_['title'] = p + + schema_['__schema_name__'] = p + + return schema_ + + def get_schema_for_endpoint(self, endpoint): + schema_ = {} + for content_type in endpoint.info.get('requestBody', {}).get('content', {}): + if 'schema' in endpoint.info['requestBody']['content'][content_type]: + schema = endpoint.info['requestBody']['content'][content_type]['schema'] + break + else: + return schema_ + + schema_ = schema.copy() + + if schema_.get('type') == 'array': + schma_ref = schema_.get('items', {}).pop('$ref') + else: + schma_ref = schema_.pop('$ref') + + if schma_ref: + schema_ref_ = self.get_schema_from_reference(schma_ref) + schema_.update(schema_ref_) + + return schema_ + + + def get_attrib_list(self): + for parent in self.menu: + for children in parent: + if children.info['operationId'] == 'get-attributes': + attributes = self.process_get(children, return_value=True, parameters={'limit': 1000} ) + attrib_names = [] + for a in attributes: + attrib_names.append(a['name']) + attrib_names.sort() + return attrib_names + + def get_enum(self, schema): + if schema['__schema_name__'] in self.enum_dict: + enum_obj = schema + + for path in self.enum_dict[schema['__schema_name__']].copy(): + for p in path.split('.'): + enum_obj = enum_obj[p] + + if not 'enum' in self.enum_dict[schema['__schema_name__']][path]: + self.enum_dict[schema['__schema_name__']][path]['enum'] = getattr(self, self.enum_dict[schema['__schema_name__']][path]['f'])() + + enum_obj['enum'] = self.enum_dict[schema['__schema_name__']][path]['enum'] + + + def get_input_for_schema_(self, schema, data={}, spacing=0, getitem=None, required_only=False): + + self.get_enum(schema) + + for prop in schema['properties']: + item = schema['properties'][prop] + if getitem and prop != getitem['__name__'] or prop in ('dn', 'inum'): + continue + + if (required_only and schema.get('required')) and not prop in schema.get('required'): + continue + + + if item['type'] == 'object' and 'properties' in item: + + print() + data_obj = {} + print("Data for object {}. {}".format(prop, item.get('description', ''))) + result = self.get_input_for_schema_(item, data_obj) + data[prop] = result + #model_name_str = item.get('__schema_name__') or item.get('title') or item.get('description') + #model_name = self.get_name_from_string(model_name_str) + + #if initialised and getattr(model, prop_): + # sub_model = getattr(model, prop_) + # self.get_input_for_schema_(item, sub_model, spacing=3, initialised=initialised) + #elif isinstance(model, type) and hasattr(swagger_client.models, model_name): + # sub_model_class = getattr(swagger_client.models, model_name) + # result = self.get_input_for_schema_(item, sub_model_class, spacing=3, initialised=initialised) + # setattr(model, prop_, result) + #elif hasattr(swagger_client.models, model.swagger_types[prop_]): + # sub_model = getattr(swagger_client.models, model.swagger_types[prop_]) + # result = self.get_input_for_schema_(item, sub_model, spacing=3, initialised=initialised) + # setattr(model, prop_, result) + #else: + # sub_model = getattr(model, prop_) + # self.get_input_for_schema_(item, sub_model, spacing=3, initialised=initialised) + # # print(self.colored_text("Fix me: can't find model", error_color)) + + elif item['type'] == 'array' and '__schema_name__' in item: + + sub_data_list = [] + sub_data_list_title_text = item.get('title') or item.get('description') or prop + sub_data_list_selection = 'y' + print(sub_data_list_title_text) + while sub_data_list_selection == 'y': + sub_data = {} + self.get_input_for_schema_(item, sub_data, spacing=spacing + 3) + sub_data_list.append(sub_data) + sub_data_list_selection = self.get_input( + text="Add another {}?".format(sub_data_list_title_text), values=['y', 'n']) + data[prop] = sub_data_list + + else: + + default = data.get(prop) or item.get('default') + values_ = item.get('enum',[]) + + if isinstance(default, property): + default = None + enforce = True if item['type'] == 'boolean' else False + + if prop in schema.get('required', []): + enforce = True + if not values_ and item['type'] == 'array' and 'enum' in item['items']: + values_ = item['items']['enum'] + if item['type'] == 'object' and not default: + default = {} + + val = self.get_input( + values=values_, + text=prop, + default=default, + itype=item['type'], + help_text=item.get('description'), + sitype=item.get('items', {}).get('type'), + enforce=enforce, + example=item.get('example'), + spacing=spacing, + iformat=item.get('format') + ) + + data[prop] = val + + return data + + + def post_requests(self, endpoint, data): + url = 'https://{}{}'.format(self.host, endpoint.path) + security = self.get_scope_for_endpoint(endpoint) + self.get_access_token(security) + mime_type = self.get_mime_for_endpoint(endpoint) + + headers = self.get_request_header({'Accept': 'application/json', 'Content-Type': mime_type}) + + response = requests.post(url, + headers=headers, + json=data, + verify=self.verify_ssl, + cert=self.mtls_client_cert + ) + self.log_response(response) + + try: + return response.json() + except: + self.print_exception(response.text) + + def process_post(self, endpoint): + schema = self.get_schema_for_endpoint(endpoint) + + if schema: + + title = schema.get('description') or schema['title'] + data_dict = {} + + if my_op_mode == 'scim': + if endpoint.path == '/jans-scim/restv1/v2/Groups': + schema['properties']['schemas']['default'] = ['urn:ietf:params:scim:schemas:core:2.0:Group'] + elif endpoint.path == '/jans-scim/restv1/v2/Users': + schema['properties']['schemas']['default'] = ['urn:ietf:params:scim:schemas:core:2.0:User'] + if endpoint.info['operationId'] == 'create-user': + schema['required'] = ['userName', 'name', 'displayName', 'emails', 'password'] + + data = self.get_input_for_schema_(schema, data_dict, required_only=True) + + optional_fields = [] + required_fields = schema.get('required', []) + ['dn', 'inum'] + for field in schema['properties']: + if not field in required_fields: + optional_fields.append(field) + + if optional_fields: + fill_optional = self.get_input(values=['y', 'n'], text='Populate optional fields?') + fields_numbers = [] + if fill_optional == 'y': + print("Optional Fields:") + for i, field in enumerate(optional_fields): + print(i + 1, field) + fields_numbers.append(str(i + 1)) + + while True: + optional_selection = self.get_input(values=['q', 'x', 'c'] + fields_numbers, + help_text="c: continue, #: populate field") + if optional_selection == 'c': + break + if optional_selection in fields_numbers: + item_name = optional_fields[int(optional_selection) - 1] + schema_item = schema['properties'][item_name].copy() + schema_item['__name__'] = item_name + item_data = self.get_input_for_schema_(schema, getitem=schema_item) + data[item_name] = item_data[item_name] + + print("Obtained Data:") + self.pretty_print(data) + + selection = self.get_input(values=['q', 'x', 'b', 'y', 'n'], text='Continue?') + + else: + selection = 'y' + model = None + + if selection == 'y': + print("Please wait while posting data ...\n") + response = self.post_requests(endpoint, data) + if response: + self.pretty_print(response) + + selection = self.get_input(values=['q', 'x', 'b']) + if selection in ('b', 'n'): + self.display_menu(endpoint.parent) + + + def delete_requests(self, endpoint, url_param_dict): + security = self.get_scope_for_endpoint(endpoint) + self.get_access_token(security) + + response = requests.delete( + url='https://{}{}'.format(self.host, endpoint.path.format(**url_param_dict)), + headers=self.get_request_header({'Accept': 'application/json'}), + verify=self.verify_ssl, + cert=self.mtls_client_cert + ) + self.log_response(response) + if response.status_code in (200, 204): + return None + + return response.text.strip() + + + def process_delete(self, endpoint): + url_param = self.get_endpiont_url_param(endpoint) + if url_param: + url_param_val = self.get_input(text=url_param['name'], help_text='Entry to be deleted') + else: + url_param_val = '' + selection = self.get_input(text="Are you sure want to delete {} ?".format(url_param_val), + values=['b', 'y', 'n', 'q', 'x']) + if selection in ('b', 'n'): + self.display_menu(endpoint.parent) + elif selection == 'y': + print("Please wait while deleting {} ...\n".format(url_param_val)) + + url_param_dict = {url_param['name']: url_param_val} + + response = self.delete_requests(endpoint, url_param_dict) + + if response is None: + print(self.colored_text("\nEntry {} was deleted successfully\n".format(url_param_val), success_color)) + else: + print(self.colored_text("An error ocurred while deleting entry:", error_color)) + print(self.colored_text(response, error_color)) + + selection = self.get_input(['b', 'q', 'x']) + if selection == 'b': + self.display_menu(endpoint.parent) + + def patch_requests(self, endpoint, url_param_dict, data): + url = 'https://{}{}'.format(self.host, endpoint.path.format(**url_param_dict)) + security = self.get_scope_for_endpoint(endpoint) + self.get_access_token(security) + + headers = self.get_request_header({'Accept': 'application/json', 'Content-Type': 'application/json-patch+json'}) + data = data + response = requests.patch( + url=url, + headers=headers, + json=data, + verify=self.verify_ssl, + cert=self.mtls_client_cert + ) + self.log_response(response) + try: + return response.json() + except: + self.print_exception(response.text) + + + def process_patch(self, endpoint): + + schema = self.get_schema_for_endpoint(endpoint) + + if 'PatchOperation' in cfg_yml['components']['schemas']: + schema = cfg_yml['components']['schemas']['PatchOperation'].copy() + schema['__schema_name__'] = 'PatchOperation' + else: + schema = cfg_yml['components']['schemas']['PatchRequest'].copy() + schema['__schema_name__'] = 'PatchRequest' + + + for item in schema['properties']: + print("d-checking", item) + if not 'type' in schema['properties'][item] or schema['properties'][item]['type']=='object': + schema['properties'][item]['type'] = 'string' + + url_param_dict = {} + url_param_val = None + url_param = self.get_endpiont_url_param(endpoint) + if 'name' in url_param: + url_param_val = self.get_input(text=url_param['name'], help_text='Entry to be patched') + url_param_dict = {url_param['name']: url_param_val} + + body = [] + + while True: + data = self.get_input_for_schema_(schema) + #guessed_val = self.guess_bool(data.value) + #if not guessed_val is None: + # data.value = guessed_val + if my_op_mode != 'scim' and not data['path'].startswith('/'): + data['path'] = '/' + data['path'] + + if my_op_mode == 'scim': + data['path'] = data['path'].replace('/', '.') + + body.append(data) + selection = self.get_input(text='Another patch operation?', values=['y', 'n']) + if selection == 'n': + break + + if endpoint.info['operationId'] == 'patch-oauth-uma-resources-by-id': + for patch_item in body: + if patch_item['path'] == '/clients': + patch_item['value'] = patch_item['value'].split('_,') + + self.pretty_print(body) + + selection = self.get_input(values=['y', 'n'], text='Continue?') + + if selection == 'y': + + if my_op_mode == 'scim': + body = {'schemas': ['urn:ietf:params:scim:api:messages:2.0:PatchOp'], 'Operations': body} + + self.patch_requests(endpoint, url_param_dict, body) + + selection = self.get_input(['b']) + if selection == 'b': + self.display_menu(endpoint.parent) + + def get_mime_for_endpoint(self, endpoint, req='requestBody'): + for key in endpoint.info[req]['content']: + return key + + + def put_requests(self, endpoint, data): + + security = self.get_scope_for_endpoint(endpoint) + self.get_access_token(security) + + mime_type = self.get_mime_for_endpoint(endpoint) + + response = requests.put( + url='https://{}{}'.format(self.host, endpoint.path), + headers=self.get_request_header({'Accept': mime_type}), + json=data, + verify=self.verify_ssl, + cert=self.mtls_client_cert + ) + self.log_response(response) + try: + result = response.json() + except Exception: + self.exit_with_error(response.text) + + return result + + + def process_put(self, endpoint): + + schema = self.get_schema_for_endpoint(endpoint) + + cur_data = None + go_back = False + + if endpoint.info.get('x-cli-getdata') != '_file': + if 'x-cli-getdata' in endpoint.info and endpoint.info['x-cli-getdata'] != None: + for m in endpoint.parent: + if m.info['operationId'] == endpoint.info['x-cli-getdata']: + while True: + try: + print("cur_data-1") + cur_data = self.process_get(m, return_value=True) + break + except ValueError as e: + retry = self.get_input(values=['y', 'n'], text='Retry?') + if retry == 'n': + self.display_menu(endpoint.parent) + break + get_endpoint = m + break + + else: + for mi in endpoint.parent : + if mi.method == 'get' and not mi.path.endswith('}'): + cur_data = self.process_get(mi, noprompt=True, update=True) + values = ['b', 'q', 'x'] + cur_data[0] + item_number = self.get_input(text="Enter item # to update", values=values) + + cur_data = cur_data[1][int(item_number) -1] + """ + for m in endpoint.parent: + if m.method == 'get' and m.path.endswith('}'): + while True: + while True: + try: + + key_name_desc = self.get_endpiont_url_param(m) + if key_name_desc and 'name' in key_name_desc: + key_name = key_name_desc['name'] + print("P-X", m) + cur_data = self.process_get(m, return_value=True) + break + except ValueError as e: + retry = self.get_input(values=['y', 'n'], text='Retry?') + if retry == 'n': + self.display_menu(endpoint.parent) + break + + if cur_data is not None: + break + + get_endpoint = m + break + """ + if not cur_data: + for m in endpoint.parent: + if m.method == 'get' and not m.path.endswith('}'): + cur_data = self.process_get(m, return_value=True) + get_endpoint = m + + end_point_param = self.get_endpiont_url_param(endpoint) + + if endpoint.info.get('x-cli-getdata') == '_file': + + # TODO: To be implemented + schema_desc = schema.get('description') or schema['__schema_name__'] + text = 'Enter filename to load data for «{}»: '.format(schema_desc) + data_fn = input(self.colored_text(text, 244)) + if data_fn == 'b': + go_back = True + elif data_fn == 'q': + sys.exit() + else: + data_org = self.get_json_from_file(data_fn) + + data = {} + for k in data_org: + if k in cur_model.attribute_map: + mapped_key = cur_model.attribute_map[k] + data[mapped_key] = data_org[k] + else: + data[k] = data_org[k] + + print("Please wait while posting data ...\n") + + response = self.put_requests(endpoint, cur_data) + + if response: + self.pretty_print(response) + + selection = self.get_input(values=['q', 'x', 'b']) + if selection == 'b': + self.display_menu(endpoint.parent) + + else: + + end_point_param_val = None + if end_point_param and end_point_param['name'] in cur_data: + end_point_param_val = cur_data[end_point_param['name']] + + schema = self.get_schema_for_endpoint(endpoint) + if schema['properties'].get('keys', {}).get('properties'): + schema = schema['properties']['keys'] + + + attr_name_list = list(schema['properties'].keys()) + if 'dn' in attr_name_list: + attr_name_list.remove('dn') + + attr_name_list.sort() + item_numbers = [] + + def print_fields(): + print("Fields:") + for i, attr_name in enumerate(attr_name_list): + print(str(i + 1).rjust(2), attr_name) + item_numbers.append(str(i + 1)) + + print_fields() + changed_items = [] + selection_list = ['q', 'x', 'b', 'v', 's', 'l'] + item_numbers + help_text = 'q: quit, v: view, s: save, l: list fields #: update field' + + while True: + selection = self.get_input(values=selection_list, help_text=help_text) + if selection == 'v': + self.pretty_print(cur_data) + elif selection == 'l': + print_fields() + elif selection in item_numbers: + item = attr_name_list[int(selection) - 1] + + schema_item = schema['properties'][item] + schema_item['__name__'] = item + self.get_input_for_schema_(schema, data=cur_data, getitem=schema_item) + changed_items.append(item) + + if selection == 'b': + self.display_menu(endpoint.parent) + break + elif selection == 's': + print('Changes:') + for ci in changed_items: + str_val = str(cur_data[ci]) + print(self.colored_text(ci, bold_color) + ':', self.colored_text(str_val, success_color)) + + selection = self.get_input(values=['y', 'n'], text='Continue?') + + if selection == 'y': + print("Please wait while posting data ...\n") + put_pname = self.get_url_param(endpoint.path) + + response = self.put_requests(endpoint, cur_data) + + if response: + self.pretty_print(response) + go_back = True + break + + if go_back: + selection = self.get_input(values=['q', 'x', 'b']) + if selection == 'b': + self.display_menu(endpoint.parent) + else: + self.get_input_for_schema_(schema, data=cur_data) + + def display_menu(self, menu): + clear() + self.current_menu = menu + + name_list = [menu.name] + par = menu + while True: + par = par.parent + if not par: + break + name_list.insert(0, par.name) + + if len(name_list) > 1: + del name_list[0] + + self.print_underlined(': '.join(name_list)) + + selection_values = ['q', 'x', 'b'] + + menu_numbering = {} + + c = 0 + for i, item in enumerate(menu): + if item.info.get('x-cli-ignore') or (item.parent.name == 'Main Menu' and not item.children): + continue + + print(c + 1, item) + selection_values.append(str(c + 1)) + menu_numbering[c + 1] = i + c += 1 + + selection = self.get_input(selection_values) + + if selection == 'b' and not menu.parent: + print("Quiting...") + sys.exit() + elif selection == 'b': + self.display_menu(menu.parent) + elif int(selection) in menu_numbering and menu.get_child(menu_numbering[int(selection)]).children: + self.display_menu(menu.get_child(menu_numbering[int(selection)])) + else: + m = menu.get_child(menu_numbering[int(selection)]) + getattr(self, 'process_' + m.method)(m) + + def parse_command_args(self, args): + args_dict = {} + + if args: + for arg in args.split(','): + neq = arg.find(':') + if neq > 1: + arg_name = arg[:neq].strip() + arg_val = arg[neq + 1:].strip() + if arg_name and arg_val: + args_dict[arg_name] = arg_val + + return args_dict + + def parse_args(self, args, path): + param_names = [] + if not 'parameters' in path: + return {} + for param in path['parameters']: + param_names.append(param['name']) + + args_dict = self.parse_command_args(args) + + for arg_name in args_dict: + if not arg_name in param_names: + self.exit_with_error("valid endpoint args are: {}".format(', '.join(param_names))) + + return args_dict + + def help_for(self, op_name): + + schema_path = None + + for path_name in cfg_yml['paths']: + for method in cfg_yml['paths'][path_name]: + path = cfg_yml['paths'][path_name][method] + if isinstance(path, dict): + for tag_ in path['tags']: + tag = get_named_tag(tag_) + if tag != op_name: + continue + + print('Operation ID:', path['operationId']) + print(' Description:', path['description']) + if path.get('__urlsuffix__'): + print(' url-suffix:', path['__urlsuffix__']) + if 'parameters' in path: + param_names = [] + for param in path['parameters']: + desc = param.get('description', 'No description is provided for this parameter') + param_type = param.get('schema', {}).get('type') + if param_type: + desc += ' [{}]'.format(param_type) + param_names.append((param['name'], desc)) + if param_names: + print(' Parameters:') + for param in param_names: + print(' {}: {}'.format(param[0], param[1])) + + if 'requestBody' in path: + for apptype in path['requestBody'].get('content', {}): + if 'schema' in path['requestBody']['content'][apptype]: + if path['requestBody']['content'][apptype]['schema'].get('type') == 'array': + schema_path = path['requestBody']['content'][apptype]['schema']['items']['$ref'] + print(' Schema: Array of {}'.format(schema_path[1:])) + else: + schema_path = path['requestBody']['content'][apptype]['schema']['$ref'] + print(' Schema: {}'.format(schema_path[1:])) + + if schema_path: + print() + print("To get sample schema type {0} --schema , for example {0} --schema {1}".format(sys.argv[0], + schema_path[ + 1:])) + + def render_json_entry(self, val): + if isinstance(val, str) and val.startswith('_file '): + file_path = val[6:].strip() + if os.path.exists(file_path): + with open(file_path) as f: + val = f.read() + else: + raise ValueError("File '{}' not found".format(file_path)) + return val + + def get_json_from_file(self, data_fn): + + if not os.path.exists(data_fn): + self.exit_with_error("Can't find file {}".format(data_fn)) + + try: + with open(data_fn) as f: + data = json.load(f) + except: + self.exit_with_error("Error parsing json file {}".format(data_fn)) + + if isinstance(data, list): + for entry in data: + if isinstance(entry, dict): + for k in entry: + entry[k] = self.render_json_entry(entry[k]) + + if isinstance(data, dict): + for k in data: + data[k] = self.render_json_entry(data[k]) + + return data + + + def process_command_get(self, path, suffix_param, endpoint_params, data_fn, data=None): + endpoint = self.get_fake_endpoint(path) + response = self.get_requests(endpoint, endpoint_params) + if not self.wrapped: + self.pretty_print(response) + else: + return response + + def exit_with_error(self, error_text): + error_text += '\n' + sys.stderr.write(self.colored_text(error_text, error_color)) + print() + sys.exit() + + + def get_fake_endpoint(self, path): + endpoint = SimpleNamespace() + endpoint.path = path['__path__'] + endpoint.info = path + return endpoint + + + def print_response(self, response): + if response: + sys.stderr.write("Server Response:\n") + self.pretty_print(response) + + def process_command_post(self, path, suffix_param, endpoint_params, data_fn, data): + + # TODO: suffix_param, endpoint_params + + endpoint = self.get_fake_endpoint(path) + + if not data: + + if data_fn.endswith('jwt'): + with open(data_fn) as reader: + data = jwt.decode(reader.read(), + options={"verify_signature": False, "verify_exp": False, "verify_aud": False}) + else: + try: + data = self.get_json_from_file(data_fn) + except ValueError as ve: + self.exit_with_error(str(ve)) + + if path['__method__'] == 'post': + response = self.post_requests(endpoint, data) + elif path['__method__'] == 'put': + response = self.put_requests(endpoint, data) + + self.print_response(response) + + def process_command_put(self, path, suffix_param, endpoint_params, data_fn, data=None): + self.process_command_post(path, suffix_param, endpoint_params, data_fn, data=None) + + def process_command_patch(self, path, suffix_param, endpoint_params, data_fn, data=None): + # TODO: suffix_param, endpoint_params + + endpoint = self.get_fake_endpoint(path) + + if not data: + try: + data = self.get_json_from_file(data_fn) + except ValueError as ve: + self.exit_with_error(str(ve)) + + if not isinstance(data, list): + self.exit_with_error("{} must be array of /components/schemas/PatchRequest".format(data_fn)) + + op_modes = ('add', 'remove', 'replace', 'move', 'copy', 'test') + + for item in data: + if not item['op'] in op_modes: + print("op must be one of {}".format(', '.join(op_modes))) + sys.exit() + if not item['path'].startswith('/'): + item['path'] = '/' + item['path'] + + response = self.patch_requests(endpoint, suffix_param, data) + + self.print_response(response) + + + def process_command_delete(self, path, suffix_param, endpoint_params, data_fn, data=None): + endpoint = self.get_fake_endpoint(path) + response = self.delete_requests(endpoint, suffix_param) + if response: + self.print_response(response) + else: + print(self.colored_text("Object was successfully deleted.", success_color)) + + def process_command_by_id(self, operation_id, url_suffix, endpoint_args, data_fn, data=None): + path = self.get_path_by_id(operation_id) + + if not path: + self.exit_with_error("No such Operation ID") + + suffix_param = self.parse_command_args(url_suffix) + endpoint_params = self.parse_command_args(endpoint_args) + + if path.get('__urlsuffix__') and not path['__urlsuffix__'] in suffix_param: + self.exit_with_error("This operation requires a value for url-suffix {}".format(path['__urlsuffix__'])) + + endpoint = Menu('', info=path) + schema = self.get_schema_for_endpoint(endpoint) + + if not data: + op_path = self.get_path_by_id(operation_id) + if op_path['__method__'] == 'patch' and not data_fn: + pop, pdata = '', '' + if args.patch_add: + pop = 'add' + pdata = args.patch_add + elif args.patch_remove: + pop = 'remove' + pdata = args.patch_remove + elif args.patch_replace: + pop = 'replace' + pdata = args.patch_replace + + if pop: + if pop != 'remove' and pdata.count(':') != 1: + self.exit_with_error("Please provide --patch-data as colon delimited key:value pair") + + if pop != 'remove': + ppath, pval = pdata.split(':') + data = [{'op': pop, 'path': '/'+ ppath.lstrip('/'), 'value': pval}] + else: + data = [{'op': pop, 'path': '/'+ pdata.lstrip('/')}] + + if (schema and not data_fn) and not data: + self.exit_with_error("Please provide schema with --data argument") + + caller_function = getattr(self, 'process_command_' + path['__method__']) + return caller_function(path, suffix_param, endpoint_params, data_fn, data=data) + + + def get_sample_schema(self, ref): + schema = self.get_schema_from_reference('#' + args.schema) + sample_schema = OrderedDict() + for prop_name in schema.get('properties', {}): + prop = schema['properties'][prop_name] + if 'default' in prop: + sample_schema[prop_name] = prop['default'] + elif 'example' in prop: + sample_schema[prop_name] = prop['example'] + elif 'enum' in prop: + sample_schema[prop_name] = random.choice(prop['enum']) + elif prop.get('type') == 'object': + sample_schema[prop_name] = {} + elif prop.get('type') == 'array': + if 'items' in prop: + if 'enum' in prop['items']: + sample_schema[prop_name] = [random.choice(prop['items']['enum'])] + elif 'type' in prop['items']: + sample_schema[prop_name] = [prop['items']['type']] + else: + sample_schema[prop_name] = [] + elif prop.get('type') == 'boolean': + sample_schema[prop_name] = random.choice((True, False)) + elif prop.get('type') == 'integer': + sample_schema[prop_name] = random.randint(1,200) + else: + sample_schema[prop_name]='string' + + print(json.dumps(sample_schema, indent=2)) + + def runApp(self): + clear() + self.display_menu(self.menu) + + +def main(): + + + error_log_file = os.path.join(log_dir, 'cli_eorror.log') + cli_object = JCA_CLI(host, client_id, client_secret, access_token, test_client) + + if not os.path.exists(log_dir): + os.makedirs(log_dir) + + try: + if not access_token: + cli_object.check_connection() + if not (args.operation_id or args.info or args.schema): + # reset previous color + print('\033[0m', end='') + cli_object.runApp() + else: + print() + if args.info: + cli_object.help_for(args.info) + elif args.schema: + cli_object.get_sample_schema(args.schema) + elif args.operation_id: + cli_object.process_command_by_id(args.operation_id, args.url_suffix, args.endpoint_args, args.data) + print() + except Exception as e: + if os.environ.get('errstdout'): + print(traceback.print_exc()) + print(u"\u001b[38;5;{}mAn Unhandled error raised: {}\u001b[0m".format(error_color, e)) + with open(error_log_file, 'a') as w: + traceback.print_exc(file=w) + print("Error is logged to {}".format(error_log_file)) + + +if __name__ == "__main__": + main() diff --git a/jans-cli-tui/cli/jca.yaml b/jans-cli-tui/cli/jca.yaml new file mode 100644 index 00000000000..38b9094d3e8 --- /dev/null +++ b/jans-cli-tui/cli/jca.yaml @@ -0,0 +1,7015 @@ +openapi: 3.0.1 +info: + title: jans-config-api + description: jans-config-api - Authorization services + contact: + email: xxx@gluu.org + license: + name: License + url: 'https://github.com/JanssenProject/blob/master/LICENSE' + version: '1.0.0' +servers: + - url: 'https://jans.io/' +tags: + - name: developers + description: jans-config-api enables access to the features available via the existing Jans Authorization Server API. + - name: Attribute + - name: Default Authentication Method + - name: Cache Configuration + - name: Cache Configuration – Memcached + - name: Cache Configuration – Redis + - name: Cache Configuration – in-Memory + - name: Cache Configuration – Native-Persistence + - name: Configuration – Properties + - name: Fido2 - Configuration + - name: Configuration – SMTP + - name: Configuration – Logging + - name: Configuration – JWK - JSON Web Key (JWK) + - name: Custom Scripts + - name: Database - LDAP configuration + - name: Database - Couchbase configuration + - name: OAuth - OpenID Connect - Clients + - name: OAuth - UMA Resources + - name: OAuth - Scopes + - name: Statistics - User + - name: Health - Check + - name: Server Stats + - name: Configuration – User Management + - name: SCIM - Config Management + - name: Organization Configuration + - name: Auth Server Health - Check + - name: Admin UI - Role + - name: Admin UI - Permission + - name: Admin UI - Role-Permissions Mapping + - name: Admin UI - License +paths: + /jans-config-api/api/v1/jans-auth-server/config: + get: + summary: Gets all Jans authorization server configuration properties. + description: Gets all Jans authorization server configuration properties. + operationId: get-properties + security: + - oauth2: [https://jans.io/oauth/jans-auth-server/config/properties.readonly] + tags: + - Configuration – Properties + responses: + '200': + description: OK + content: + application/json: + schema: + title: AppConfiguration + description: Jans Authorization Server config properties. + $ref: '#/components/schemas/AppConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + patch: + summary: Partially modifies Jans authorization server Application configuration properties. + description: Partially modifies Jans authorization server AppConfiguration properties. + operationId: patch-properties + security: + - oauth2: [https://jans.io/oauth/jans-auth-server/config/properties.write] + tags: + - Configuration – Properties + requestBody: + content: + application/json-patch+json: + schema: + type: array + items: + $ref: '#/components/schemas/PatchRequest' + description: String representing patch-document. + example: '[ {op:replace, path: loggingLevel, value: DEBUG } ]' + responses: + '200': + description: OK + content: + application/json: + schema: + title: AppConfiguration + description: Jans authorization server config properties. + $ref: '#/components/schemas/AppConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + /jans-config-api/api/v1/jans-auth-server/config/persistence: + get: + summary: Returns persistence type configured for Jans authorization server. + description: Returns persistence type configured for Jans authorization server. + operationId: get-properties-persistence + security: + - oauth2: [https://jans.io/oauth/jans-auth-server/config/properties.readonly] + tags: + - Configuration – Properties + responses: + '200': + description: OK + content: + application/json: + schema: + title: PersistenceConfiguration + description: Jans Authorization Persistence Configuration object. + $ref: '#/components/schemas/PersistenceConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + /jans-config-api/fido2/config: + get: + summary: Gets Jans Authorization Server Fido2 configuration properties. + description: Gets Jans Authorization Server Fido2 configuration properties. + operationId: get-properties-fido2 + tags: + - Fido2 - Configuration + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/JansFido2DynConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/fido2.readonly] + put: + summary: Updates Fido2 configuration properties. + description: Updates Fido2 configuration properties. + operationId: put-properties-fido2 + tags: + - Fido2 - Configuration + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/JansFido2DynConfiguration' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/JansFido2DynConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/fido2.write] + parameters: [] + /jans-config-api/fido2/registration/entries/{username}: + parameters: + - name: username + in: path + required: true + description: Username. + schema: + type: string + get: + summary: Get details of connected FIDO2 devices registered to user. + description: Get details of connected FIDO2 devices registered to user. + operationId: get-registration-entries-fido2 + tags: + - Fido2 - Configuration + responses: + '200': + description: OK + content: + application/json: + schema: + title: FIDO2 registered devices + description: List of all FIDO2 registered devices. + type: array + items: + $ref: '#/components/schemas/Fido2RegistrationEntry' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [ https://jans.io/oauth/config/fido2.readonly ] + /jans-config-api/api/v1/attributes: + get: + summary: Gets a list of Gluu attributes. + description: 'Gets all attributes. Optionally max-size of the result, attribute status and pattern can be provided.' + operationId: get-attributes + tags: + - Attribute + responses: + '200': + description: OK + content: + application/json: + schema: + title: Gluu Attributes + description: List of all attribute. + type: array + items: + $ref: '#/components/schemas/GluuAttribute' + '401': + description: Unauthorized + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/attributes.readonly] + parameters: + - schema: + type: integer + default: 50 + in: query + name: limit + description: Search size - max size of the results to return. + - schema: + type: string + in: query + name: pattern + description: Search pattern. + - schema: + type: string + default: all + in: query + name: status + description: Status of the attribute + post: + summary: Adds a new attribute. + description: Adds a new attribute. + operationId: post-attributes + tags: + - Attribute + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/GluuAttribute' + responses: + '201': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/GluuAttribute' + '401': + description: Unauthorized + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/attributes.write] + put: + summary: Updates an existing attribute. + description: Updates an existing attribute. + operationId: put-attributes + tags: + - Attribute + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/GluuAttribute' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/GluuAttribute' + '401': + description: Unauthorized + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/attributes.write] + parameters: [] + '/jans-config-api/api/v1/attributes/{inum}': + parameters: + - name: inum + in: path + required: true + description: Attribute ID. + schema: + type: string + get: + summary: Gets an attribute based on inum. + description: Gets an attribute based on inum. + operationId: get-attributes-by-inum + tags: + - Attribute + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/GluuAttribute' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/attributes.readonly] + delete: + summary: Deletes an attribute based on inum. + description: Deletes an attribute based on inum. + operationId: delete-attributes-by-inum + tags: + - Attribute + responses: + '204': + description: No Content + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/attributes.delete] + patch: + summary: Partially modify a GluuAttribute. + description: Partially modify a GluuAttribute. + operationId: patch-attributes-by-inum + security: + - oauth2: [https://jans.io/oauth/config/attributes.write] + tags: + - Attribute + requestBody: + content: + application/json-patch+json: + schema: + type: array + items: + $ref: '#/components/schemas/PatchRequest' + description: String representing patch-document. + example: '[ {op:replace, path: displayName, value: \"CustomAttribute\" } ]' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/GluuAttribute' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + /jans-config-api/api/v1/acrs: + get: + summary: Gets default authentication method. + description: Gets default authentication method. + operationId: get-acrs + tags: + - Default Authentication Method + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/AuthenticationMethod' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/acrs.readonly] + put: + summary: Updates default authentication method. + description: Updates default authentication method. + operationId: put-acrs + tags: + - Default Authentication Method + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AuthenticationMethod' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/AuthenticationMethod' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/acrs.write] + parameters: [] + /jans-config-api/api/v1/config/database/ldap: + get: + summary: Gets list of existing LDAP configurations. + description: Gets list of existing LDAP configurations. + operationId: get-config-database-ldap + tags: + - Database - LDAP configuration + responses: + '200': + description: OK + content: + application/json: + schema: + title: LdapConfiguration + description: List of configured LDAP configuration. + type: array + items: + $ref: '#/components/schemas/LdapConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/database/ldap.readonly] + post: + summary: Adds a new LDAP configuration. + description: Adds a new LDAP configuration. + operationId: post-config-database-ldap + tags: + - Database - LDAP configuration + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/LdapConfiguration' + responses: + '201': + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/LdapConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/database/ldap.write] + put: + summary: Updates LDAP configuration. + description: Updates LDAP configuration. + operationId: put-config-database-ldap + tags: + - Database - LDAP configuration + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/LdapConfiguration' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/LdapConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/database/ldap.write] + parameters: [] + /jans-config-api/api/v1/config/database/ldap/{name}: + parameters: + - name: name + in: path + required: true + description: Name of LDAP configuration. + schema: + type: string + get: + summary: Gets an LDAP configuration by name. + description: Gets an LDAP configuration by name. + operationId: get-config-database-ldap-by-name + tags: + - Database - LDAP configuration + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/LdapConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/database/ldap.readonly] + delete: + summary: Deletes an LDAP configuration. + description: Deletes an LDAP configuration. + operationId: delete-config-database-ldap-by-name + tags: + - Database - LDAP configuration + responses: + '204': + description: No Content + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/database/ldap.delete] + patch: + summary: Partially modify an LDAP configuration. + description: Partially modify an LDAP configuration. + operationId: patch-config-database-ldap-by-name + security: + - oauth2: [https://jans.io/oauth/config/database/ldap.write] + tags: + - Database - LDAP configuration + requestBody: + content: + application/json-patch+json: + schema: + type: array + items: + $ref: '#/components/schemas/PatchRequest' + description: String representing patch-document. + example: '[ {op:replace, path: maxConnections, value: 8 } ]' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/LdapConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + /jans-config-api/api/v1/config/database/ldap/test: + post: + summary: Tests an LDAP configuration. + description: Tests an LDAP configuration. + operationId: post-config-database-ldap-test + tags: + - Database - LDAP configuration + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/LdapConfiguration' + responses: + '200': + description: OK + content: + application/json: + schema: + type: boolean + description: LDAP connection status true if connection is successfully established. + + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/database/ldap.readonly] + /jans-config-api/api/v1/config/database/sql: + get: + summary: Gets list of existing sql configurations. + description: Gets list of existing sql configurations. + operationId: get-config-database-sql + tags: + - Database - Sql configuration + responses: + '200': + description: OK + content: + application/json: + schema: + title: SqlConfiguration + description: List of configured Sql configuration. + items: + $ref: '#/components/schemas/SqlConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/database/sql.readonly] + post: + summary: Adds a new Sql configuration. + description: Adds a new Sql configuration. + operationId: post-config-database-sql + tags: + - Database - Sql configuration + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SqlConfiguration' + responses: + '201': + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/SqlConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/database/sql.write] + put: + summary: Updates Sql configuration. + description: Updates Sql configuration. + operationId: put-config-database-sql + tags: + - Database - Sql configuration + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SqlConfiguration' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/SqlConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/database/sql.write] + parameters: [] + /jans-config-api/api/v1/config/database/sql/{name}: + parameters: + - name: name + in: path + required: true + description: Name of Sql configuration. + schema: + type: string + get: + summary: Gets a Sql configurations by name. + description: Gets a Sql configurations by name. + operationId: get-config-database-sql-by-name + tags: + - Database - Sql configuration + responses: + '200': + description: OK + content: + application/json: + schema: + title: SqlConfiguration + description: List of configured Sql configuration. + $ref: '#/components/schemas/SqlConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/database/sql.readonly] + patch: + summary: Partially modify an Sql configuration. + description: Partially modify an Sql configuration. + operationId: patch-config-database-sql-by-name + tags: + - Database - Sql configuration + requestBody: + content: + application/json-patch+json: + schema: + type: array + items: + $ref: '#/components/schemas/PatchRequest' + description: String representing patch-document. + example: '[ {op:replace, path: maxConnections, value: 8 } ]' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/SqlConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/database/sql.write] + delete: + summary: Deletes a Sql configurations by name. + description: Deletes a Sql configurations by name. + operationId: delete-config-database-sql-by-name + tags: + - Database - Sql configuration + responses: + '204': + description: No Content + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/database/sql.delete] + /jans-config-api/api/v1/config/database/sql/test: + post: + summary: Tests a Sql configuration. + description: Tests a Sql configuration. + operationId: post-config-database-sql-test + tags: + - Database - Sql configuration + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SqlConfiguration' + responses: + '200': + description: OK + content: + application/json: + schema: + type: boolean + description: Sql connection status true if connection is successfully established. + + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/database/sql.readonly] + /jans-config-api/api/v1/config/database/couchbase: + get: + summary: Gets list of existing Couchbase configurations. + description: Gets list of existing Couchbase configurations. + operationId: get-config-database-couchbase + tags: + - Database - Couchbase configuration + responses: + '200': + description: OK + content: + application/json: + schema: + title: CouchbaseConfiguration + description: List of configured Couchbase configuration. + items: + $ref: '#/components/schemas/CouchbaseConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/database/couchbase.readonly] + post: + summary: Adds a new Couchbase configuration. + description: Adds a new Couchbase configuration. + operationId: post-config-database-couchbase + tags: + - Database - Couchbase configuration + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CouchbaseConfiguration' + responses: + '201': + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/CouchbaseConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/database/couchbase.write] + put: + summary: Updates Couchbase configuration. + description: Updates Couchbase configuration. + operationId: put-config-database-couchbase + tags: + - Database - Couchbase configuration + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CouchbaseConfiguration' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CouchbaseConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/database/couchbase.write] + parameters: [] + /jans-config-api/api/v1/config/database/couchbase/{name}: + parameters: + - name: name + in: path + required: true + description: Name of Couchbase configuration. + schema: + type: string + get: + summary: Gets a Couchbase configurations by name. + description: Gets a Couchbase configurations by name. + operationId: get-config-database-couchbase-by-name + tags: + - Database - Couchbase configuration + responses: + '200': + description: OK + content: + application/json: + schema: + title: CouchbaseConfiguration + description: List of configured Couchbase configuration. + $ref: '#/components/schemas/CouchbaseConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/database/couchbase.readonly] + patch: + summary: Partially modify an Couchbase configuration. + description: Partially modify an Couchbase configuration. + operationId: patch-config-database-couchbase-by-name + tags: + - Database - Couchbase configuration + requestBody: + content: + application/json-patch+json: + schema: + type: array + items: + $ref: '#/components/schemas/PatchRequest' + description: String representing patch-document. + example: '[ {op:replace, path: maxConnections, value: 8 } ]' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CouchbaseConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/database/couchbase.write] + delete: + summary: Deletes a Couchbase configurations by name. + description: Deletes a Couchbase configurations by name. + operationId: delete-config-database-couchbase-by-name + tags: + - Database - Couchbase configuration + responses: + '204': + description: No Content + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/database/couchbase.delete] + /jans-config-api/api/v1/config/database/couchbase/test: + post: + summary: Tests a Couchbase configuration. + description: Tests a Couchbase configuration. + operationId: post-config-database-couchbase-test + tags: + - Database - Couchbase configuration + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CouchbaseConfiguration' + responses: + '200': + description: OK + content: + application/json: + schema: + type: boolean + description: Couchbase connection status true if connection is successfully established. + + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/database/couchbase.readonly] + /jans-config-api/api/v1/config/scripts: + get: + summary: Gets a list of custom scripts. + description: Gets a list of custom scripts. + operationId: get-config-scripts + tags: + - Custom Scripts + responses: + '200': + description: OK + content: + application/json: + schema: + items: + $ref: '#/components/schemas/CustomScript' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/scripts.readonly] + post: + summary: Adds a new custom script. + description: Adds a new custom script. + operationId: post-config-scripts + tags: + - Custom Scripts + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CustomScript' + responses: + '201': + description: CREATED + content: + application/json: + schema: + $ref: '#/components/schemas/CustomScript' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/scripts.write] + put: + summary: Updates a custom script. + description: Updates a custom script. + operationId: put-config-scripts + x-cli-getdata: get-config-scripts-by-inum + tags: + - Custom Scripts + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CustomScript' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CustomScript' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/scripts.write] + /jans-config-api/api/v1/config/scripts/type/{type}: + parameters: + - schema: + type: string + enum: + - '- person_authentication' + - '- introspection' + - '- resource_owner_password_credentials' + - '- application_session' + - '- cache_refresh' + - '- client_registration' + - '- id_generator' + - '- uma_rpt_policy' + - '- uma_rpt_claims' + - '- uma_claims_gathering' + - '- consent_gathering' + - '- dynamic_scope' + - '- spontaneous_scope' + - '- end_session' + - '- post_authn' + - '- scim' + - '- ciba_end_user_notification' + - '- persistence_extension' + - '- idp' + - 'revoke_token' + - 'discovery' + - 'update_token' + - 'config_api_auth' + name: type + in: path + description: Script type. + required: true + - schema: + type: string + in: query + name: pattern + description: Search pattern. + - schema: + type: integer + default: 50 + in: query + name: limit + description: Search size - max size of the results to return. + get: + summary: Gets list of scripts by type. + description: Gets list of scripts by type. + operationId: get-config-scripts-by-type + x-cli-ignore: true + tags: + - Custom Scripts + responses: + '200': + description: OK + content: + application/json: + schema: + items: + $ref: '#/components/schemas/CustomScript' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/scripts.readonly] + /jans-config-api/api/v1/config/scripts/inum/{inum}: + parameters: + - schema: + type: string + name: inum + in: path + required: true + description: Script identifier. + get: + summary: Gets a script by Inum. + description: Gets a script by Inum. + operationId: get-config-scripts-by-inum + x-cli-ignore: true + tags: + - Custom Scripts + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CustomScript' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/scripts.readonly] + /jans-config-api/api/v1/config/scripts/{inum}: + parameters: + - schema: + type: string + name: inum + in: path + required: true + description: Script identifier. + delete: + summary: Deletes a custom script. + description: Deletes a custom script. + operationId: delete-config-scripts-by-inum + tags: + - Custom Scripts + responses: + '204': + description: No Content + '401': + description: Unauthorized + '404': + description: Not Found + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/scripts.delete] + patch: + summary: Partially update custom script. + description: Partially update custom script. + operationId: patch-config-scripts-by-inum + security: + - oauth2: [https://jans.io/oauth/config/scripts.write] + tags: + - Custom Scripts + requestBody: + content: + application/json-patch+json: + schema: + type: array + items: + $ref: '#/components/schemas/PatchRequest' + description: String representing patch-document. + example: '[ {op:replace, path: enabled, value: \"false\" } ]' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CustomScript' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + + /jans-config-api/api/v1/config/cache: + get: + summary: Returns cache configuration. + description: Returns cache configuration. + operationId: get-config-cache + tags: + - Cache Configuration + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CacheConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/cache.readonly] + patch: + summary: Partially modifies cache configuration. + description: Partially modifies cache configuration. + operationId: patch-config-cache + tags: + - Cache Configuration + requestBody: + content: + application/json-patch+json: + schema: + type: array + items: + $ref: '#/components/schemas/PatchRequest' + description: String representing patch-document. + example: '[ {op:replace, path: memcachedConfiguration, value: response.memcachedConfiguration } ]' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CacheConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/cache.write] + /jans-config-api/api/v1/config/cache/memcached: + get: + summary: Returns Memcached cache configuration. + description: Returns Memcached cache configuration. + operationId: get-config-cache-memcached + tags: + - Cache Configuration – Memcached + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/MemcachedConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/cache.readonly] + put: + summary: Updates Memcached cache configuration. + description: Updates Memcached cache configuration. + operationId: put-config-cache-memcached + tags: + - Cache Configuration – Memcached + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/MemcachedConfiguration' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/MemcachedConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/cache.write] + parameters: [] + patch: + summary: Partially modifies Memcached cache configuration. + description: Partially modifies Memcached cache configuration. + operationId: patch-config-cache-memcached + tags: + - Cache Configuration – Memcached + requestBody: + content: + application/json-patch+json: + schema: + type: array + items: + $ref: '#/components/schemas/PatchRequest' + description: String representing patch-document. + example: '[ {op:replace, path: memcachedConfiguration, value: response.memcachedConfiguration } ]' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/MemcachedConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/cache.write] + /jans-config-api/api/v1/config/cache/redis: + get: + summary: Returns Redis cache configuration. + description: Returns Redis cache configuration. + operationId: get-config-cache-redis + tags: + - Cache Configuration – Redis + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/RedisConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/cache.readonly] + put: + summary: Updates Redis cache configuration. + description: Updates Redis cache configuration. + operationId: put-config-cache-redis + tags: + - Cache Configuration – Redis + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RedisConfiguration' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/RedisConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/cache.write] + parameters: [] + patch: + summary: Partially modifies Redis cache configuration. + description: Partially modifies Redis cache configuration. + operationId: patch-config-cache-redis + tags: + - Cache Configuration – Redis + requestBody: + content: + application/json-patch+json: + schema: + type: array + items: + $ref: '#/components/schemas/PatchRequest' + description: String representing patch-document. + example: '[ {op:replace, path: redisConfiguration/defaultPutExpiration, value: 80 } ]' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/RedisConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/cache.write] + /jans-config-api/api/v1/config/cache/in-memory: + get: + summary: Returns in-Memory cache configuration. + description: Returns in-Memory cache configuration. + operationId: get-config-cache-in-memory + tags: + - Cache Configuration – in-Memory + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/InMemoryConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/cache.readonly] + put: + summary: Updates in-Memory cache configuration. + description: Updates in-Memory cache configuration. + operationId: put-config-cache-in-memory + tags: + - Cache Configuration – in-Memory + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/InMemoryConfiguration' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/InMemoryConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/cache.write] + parameters: [] + patch: + summary: Partially modifies In-Memory cache configuration. + description: Partially modifies In-Memory cache configuration. + operationId: patch-config-cache-in-memory + tags: + - Cache Configuration – in-Memory + requestBody: + content: + application/json-patch+json: + schema: + type: array + items: + $ref: '#/components/schemas/PatchRequest' + description: String representing patch-document. + example: '[ {op:replace, path: inMemoryConfiguration/defaultPutExpiration, value: 80 } ]' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/InMemoryConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/cache.write] + /jans-config-api/api/v1/config/cache/native-persistence: + get: + summary: Returns native persistence cache configuration. + description: Returns native persistence cache configuration. + operationId: get-config-cache-native-persistence + tags: + - Cache Configuration – Native-Persistence + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/NativePersistenceConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/cache.readonly] + put: + summary: Updates native persistence cache configuration. + description: Updates native persistence cache configuration. + operationId: put-config-cache-native-persistence + tags: + - Cache Configuration – Native-Persistence + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/NativePersistenceConfiguration' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/NativePersistenceConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/cache.write] + parameters: [] + patch: + summary: Partially modifies Native Persistence cache configuration. + description: Partially modifies Native Persistence cache configuration. + operationId: patch-config-cache-native-persistence + tags: + - Cache Configuration – Native-Persistence + requestBody: + content: + application/json-patch+json: + schema: + type: array + items: + $ref: '#/components/schemas/PatchRequest' + description: String representing patch-document. + example: '[ {op:replace, path: nativePersistenceConfiguration/defaultPutExpiration, value: 80 } ]' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/NativePersistenceConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/cache.write] + /jans-config-api/api/v1/config/smtp: + get: + summary: Returns SMTP server configuration. + description: Returns SMTP server configuration. + operationId: get-config-smtp + tags: + - Configuration – SMTP + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/SmtpConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/smtp.readonly] + post: + summary: Adds SMTP server configuration. + description: Adds SMTP server configuration. + operationId: post-config-smtp + tags: + - Configuration – SMTP + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/SmtpConfiguration' + responses: + '201': + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/SmtpConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/smtp.write] + put: + summary: Updates SMTP server configuration. + description: Updates SMTP server configuration. + operationId: put-config-smtp + tags: + - Configuration – SMTP + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/SmtpConfiguration' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/SmtpConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/smtp.write] + delete: + summary: Deletes SMTP server configuration. + description: Deletes SMTP server configuration. + operationId: delete-config-smtp + tags: + - Configuration – SMTP + responses: + '204': + description: No Content + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/smtp.delete] + parameters: [] + /jans-config-api/api/v1/config/smtp/test: + post: + summary: Test SMTP server configuration. + description: Test SMTP server configuration. + operationId: test-config-smtp + tags: + - Configuration – SMTP + responses: + '200': + description: OK + content: + application/json: + schema: + type: boolean + description: SMTP test status true if email sent is successful. + '401': + $ref: '#/components/responses/Unauthorized' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/smtp.readonly] + /jans-config-api/api/v1/logging: + get: + tags: + - Configuration – Logging + summary: Returns Jans Authorization Server logging settings. + description: Returns Jans Authorization Server logging settings. + operationId: get-config-logging + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/LoggingConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/logging.readonly] + put: + tags: + - Configuration – Logging + summary: Updates Jans Authorization Server logging settings. + description: Updates Jans Authorization Server logging settings. + operationId: put-config-logging + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/LoggingConfiguration' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/LoggingConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/logging.write] + parameters: [] + /jans-config-api/api/v1/config/jwks: + get: + tags: + - Configuration – JWK - JSON Web Key (JWK) + summary: Gets list of JSON Web Key (JWK) used by server. + description: 'Gets list of JSON Web Key (JWK) used by server. JWK is a JSON data structure that represents a set of public keys as a JSON object [RFC4627].' + operationId: get-config-jwks + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/WebKeysConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/jwks.readonly] + put: + tags: + - Configuration – JWK - JSON Web Key (JWK) + summary: Puts/replaces JWKS + description: Puts/replaces JSON Web Keys (JWKS). + operationId: put-config-jwks + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/WebKeysConfiguration' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/WebKeysConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/jwks.write] + patch: + tags: + - Configuration – JWK - JSON Web Key (JWK) + summary: Patch JWKS + description: Patch JSON Web Keys (JWKS). + operationId: patch-config-jwks + requestBody: + content: + application/json-patch+json: + schema: + type: array + items: + $ref: '#/components/schemas/PatchRequest' + description: String representing patch-document. + example: '[{"op": "add","path": "/keys/1", "value": { "kty": "RSA", "e": "AQAB","use": "sig","crv": "","kid": "dd570bfb-276a-44aa-a97d-667b57587108_sig_rs256","x5c": ["MIIDBDCC..."],"exp": 1599751946863,"alg": "RS256","n": "zj1NE.."}}]' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/WebKeysConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/jwks.write] + /jans-config-api/api/v1/config/jwks/key: + post: + tags: + - Configuration – JWK - JSON Web Key (JWK) + summary: Adds a new key to JSON Web Keys (JWKS) + description: Adds a new key to JSON Web Keys (JWKS). + operationId: post-config-jwks-key + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/JsonWebKey' + responses: + '201': + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/JsonWebKey' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/jwks.write] + /jans-config-api/api/v1/config/jwks/{kid}: + parameters: + - schema: + type: string + name: kid + in: path + description: The unique identifier for the key. + required: true + get: + tags: + - Configuration – JWK - JSON Web Key (JWK) + summary: Get a JSON Web Key based on kid + description: Get a JSON Web Key based on kid + operationId: put-config-jwk-kid + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/JsonWebKey' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/jwks.readonly] + patch: + tags: + - Configuration – JWK - JSON Web Key (JWK) + summary: Patch a specific JSON Web Key based on kid + description: Patch a specific JSON Web Key based on kid + operationId: patch-config-jwk-kid + requestBody: + content: + application/json-patch+json: + schema: + type: array + items: + $ref: '#/components/schemas/PatchRequest' + description: String representing patch-document. + example: '[{"op": "add","path": "/kty", "value": "RSA"}]' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/JsonWebKey' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + security: + - oauth2: [https://jans.io/oauth/config/jwks.write] + delete: + tags: + - Configuration – JWK - JSON Web Key (JWK) + summary: Delete a JSON Web Key based on kid + description: Delete a JSON Web Key based on kid + operationId: delete-config-jwk-kid + responses: + '204': + description: No Content + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/jwks.write] + /jans-config-api/api/v1/openid/clients: + get: + tags: + - OAuth - OpenID Connect - Clients + summary: Gets list of OpenID Connect clients + description: Gets list of OpenID Connect clients + operationId: get-oauth-openid-clients + responses: + '200': + description: OK + content: + application/json: + schema: + title: OpenID Clients. + description: List of OpenID clients. + items: + $ref: '#/components/schemas/Client' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/openid/clients.readonly] + parameters: + - schema: + type: integer + default: 50 + in: query + name: limit + description: Search size - max size of the results to return. + - schema: + type: string + in: query + name: pattern + description: Search pattern. + - schema: + type: integer + default: 1 + in: query + name: startIndex + description: The 1-based index of the first query result. + - schema: + type: string + default: inum + in: query + name: sortBy + description: Attribute whose value will be used to order the returned response. + - schema: + type: string + default: ascending + enum: + - ascending + - descending + in: query + name: sortOrder + description: Order in which the sortBy param is applied. Allowed values are "ascending" and "descending". + post: + tags: + - OAuth - OpenID Connect - Clients + summary: Create new OpenId Connect client + description: Create new OpenId Connect client + operationId: post-oauth-openid-clients + requestBody: + content: + application/json: + schema: + title: OpenID Connect Client Details. + description: OpenID Connect Client Details. + $ref: '#/components/schemas/Client' + responses: + '201': + description: Created + content: + application/json: + schema: + title: OpenID Connect Client Details. + $ref: '#/components/schemas/Client' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/openid/clients.write] + put: + tags: + - OAuth - OpenID Connect - Clients + summary: Update OpenId Connect client. + description: Update OpenId Connect client. + operationId: put-oauth-openid-clients + requestBody: + content: + application/json: + schema: + title: OpenID Connect Client Details. + $ref: '#/components/schemas/Client' + responses: + '200': + description: OK + content: + application/json: + schema: + title: OpenID Connect Client Details. + $ref: '#/components/schemas/Client' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/openid/clients.write] + /jans-config-api/api/v1/openid/clients/{inum}: + parameters: + - schema: + type: string + name: inum + in: path + description: Client identifier + required: true + get: + tags: + - OAuth - OpenID Connect - Clients + summary: Get OpenId Connect Client by Inum + description: Get OpenId Connect Client by Inum. + operationId: get-oauth-openid-clients-by-inum + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Client' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/openid/clients.readonly] + delete: + tags: + - OAuth - OpenID Connect - Clients + summary: Delete OpenId Connect client. + description: Delete OpenId Connect client. + operationId: delete-oauth-openid-clients-by-inum + responses: + '204': + description: No Content + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/openid/clients.delete] + patch: + tags: + - OAuth - OpenID Connect - Clients + summary: Update modified properties of OpenId Connect client by Inum. + description: Update modified properties of OpenId Connect client by Inum. + operationId: patch-oauth-openid-clients-by-inum + requestBody: + content: + application/json-patch+json: + schema: + type: array + items: + $ref: '#/components/schemas/PatchRequest' + description: String representing patch-document. + example: '[ {op:replace, path: backchannel_authentication_request_signing_alg, value: false } ]' + responses: + '200': + description: OK + content: + application/json: + schema: + title: Client Details. + $ref: '#/components/schemas/Client' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/openid/clients.write] + /jans-config-api/api/v1/uma/resources: + get: + tags: + - OAuth - UMA Resources + summary: Gets list of UMA resources. + description: Gets list of UMA resources. + operationId: get-oauth-uma-resources + responses: + '200': + description: OK + content: + application/json: + schema: + title: UMA Resource list. + description: List of UMA Resource. + items: + $ref: '#/components/schemas/UmaResource' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/uma/resources.readonly] + parameters: + - schema: + type: integer + default: 50 + in: query + name: limit + description: Search size - max size of the results to return. + - schema: + type: string + in: query + name: pattern + description: Search pattern. + post: + tags: + - OAuth - UMA Resources + summary: Creates an UMA resource. + description: Creates an UMA resource. + operationId: post-oauth-uma-resources + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UmaResource' + responses: + '201': + description: Created + content: + application/json: + schema: + title: UMAResource + $ref: '#/components/schemas/UmaResource' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/uma/resources.write] + put: + tags: + - OAuth - UMA Resources + summary: Updates an UMA resource. + description: Updates an UMA resource. + operationId: put-oauth-uma-resources + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UmaResource' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/UmaResource' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/uma/resources.write] + /jans-config-api/api/v1/uma/resources/{id}: + parameters: + - name: id + in: path + required: true + description: Resource description ID. + schema: + type: string + get: + tags: + - OAuth - UMA Resources + summary: Gets an UMA resource by ID. + description: Gets an UMA resource by ID. + operationId: get-oauth-uma-resources-by-id + responses: + '200': + description: OK + content: + application/json: + schema: + title: UMAResource + $ref: '#/components/schemas/UmaResource' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/uma/resources.readonly] + delete: + tags: + - OAuth - UMA Resources + summary: Deletes an UMA resource. + description: Deletes an UMA resource. + operationId: delete-oauth-uma-resources-by-id + responses: + '204': + description: No Content + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/uma/resources.delete] + patch: + tags: + - OAuth - UMA Resources + summary: Partially updates an UMA resource by Inum. + description: Partially updates an UMA resource by Inum. + operationId: patch-oauth-uma-resources-by-id + requestBody: + content: + application/json-patch+json: + schema: + type: array + items: + $ref: '#/components/schemas/PatchRequest' + description: String representing patch-document. + example: '[ {op:replace, path: clients, value: [\"client_1\",\"client_2\"] },{op:add, path: clients/2, value: \"client_3\" } ]' + responses: + '200': + description: OK + content: + application/json: + schema: + title: UMAResource + $ref: '#/components/schemas/UmaResource' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/uma/resources.write] + /jans-config-api/api/v1/scopes: + get: + tags: + - OAuth - Scopes + summary: Gets list of Scopes. + description: Gets list of Scopes. Optionally type to filter the scope, max-size of the result and pattern can be provided. + operationId: get-oauth-scopes + responses: + '200': + description: OK + content: + application/json: + schema: + title: Scope description list. + description: List of scope description. + items: + $ref: '#/components/schemas/Scope' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/scopes.readonly] + parameters: + - schema: + type: string + enum: + - openid + - dynamic + - uma + - spontaneous + - oauth + in: query + name: type + description: Scope type. + - schema: + type: integer + default: 50 + in: query + name: limit + description: Search size - max size of the results to return. + - schema: + type: string + in: query + name: pattern + description: Search pattern. + post: + tags: + - OAuth - Scopes + summary: Create Scope. + description: Create Scope. + operationId: post-oauth-scopes + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Scope' + responses: + '201': + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/Scope' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/scopes.write] + put: + tags: + - OAuth - Scopes + summary: Updates existing Scope. + description: Updates existing Scope. + operationId: put-oauth-scopes + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Scope' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Scope' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/scopes.write] + parameters: [] + /jans-config-api/api/v1/scopes/{inum}: + parameters: + - schema: + type: string + name: inum + in: path + required: true + get: + tags: + - OAuth - Scopes + summary: Get Scope by Inum + description: Get Scope by Inum + operationId: get-oauth-scopes-by-inum + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Scope' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + description: Internal Server Error + security: + - oauth2: ['https://jans.io/oauth/config/scopes.readonly'] + delete: + tags: + - OAuth - Scopes + summary: Delete Scope. + description: Delete Scope. + operationId: delete-oauth-scopes-by-inum + responses: + '204': + description: No Content + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/scopes.delete] + patch: + tags: + - OAuth - Scopes + summary: Update modified attributes of existing Scope by Inum. + description: Update modified attributes of existing Scope by Inum. + operationId: patch-oauth-scopes-by-id + requestBody: + content: + application/json-patch+json: + schema: + type: array + items: + $ref: '#/components/schemas/PatchRequest' + description: String representing patch-document. + example: '[ {op:replace, path: clients, value: [\"client_1\",\"client_2\"] },{op:add, path: clients/2, value: \"client_3\" } ]' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Scope' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/scopes.write] + /jans-config-api/api/v1/stat: + get: + summary: Provides server with basic statistic. + description: Provides server with basic statistic. + operationId: get-stat + security: + - oauth2: [https://jans.io/oauth/config/stats.readonly jans_stat] + tags: + - Statistics - User + responses: + '200': + description: OK + content: + application/json: + schema: + title: FlatStatResponse + description: Jans Authorization Server statistic data. + type: array + items: + $ref: '#/components/schemas/StatResponseItem' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + parameters: + - schema: + type: string + in: query + name: month + description: Month for which the stat report is to be fetched. The parameter is mandatory if start_month and end_month parameters are not present. + example: 202012 (2020 Dec) 202101 (2021 Jan)) + required: true + - schema: + type: string + in: query + name: start_month + description: Start-Month for which the stat report is to be fetched. + - schema: + type: string + in: query + name: end_month + description: End-Month for which the stat report is to be fetched. + - schema: + type: string + enum: + - json + - openmetrics + default: json + in: query + name: format + description: Report format + + /jans-config-api/api/v1/health: + get: + summary: Returns application health status. + description: Returns application health status. + operationId: get-config-health + tags: + - Health - Check + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/HealthStatus' + '500': + description: Internal Server Error + /jans-config-api/api/v1/health/live: + get: + summary: Returns application liveness status. + description: Returns application liveness status. + operationId: get-config-health-live + tags: + - Health - Check + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/HealthStatusItem' + '500': + description: Internal Server Error + /jans-config-api/api/v1/health/ready: + get: + summary: Returns application readiness status. + description: Returns application readiness status. + operationId: get-config-health-ready + tags: + - Health - Check + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/HealthStatusItem' + '500': + description: Internal Server Error + + /jans-config-api/api/v1/health/server-stat: + get: + summary: Returns application server status. + description: Returns application server status. + operationId: get-server-stat + tags: + - Server Stats + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/StatsData' + '500': + description: Internal Server Error + + /jans-config-api/mgt/configuser: + get: + tags: + - Configuration – User Management + summary: Gets list of users + description: Gets list of users + operationId: get-user + responses: + '200': + description: OK + content: + application/json: + schema: + title: Users. + description: List of users. + items: + $ref: '#/components/schemas/CustomUser' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/user.readonly] + parameters: + - schema: + type: integer + default: 50 + in: query + name: limit + description: Search size - max size of the results to return. + - schema: + type: string + in: query + name: pattern + description: Search pattern. + - schema: + type: integer + default: 1 + in: query + name: startIndex + description: The 1-based index of the first query result. + - schema: + type: string + default: inum + in: query + name: sortBy + description: Attribute whose value will be used to order the returned response. + - schema: + type: string + default: ascending + enum: + - ascending + - descending + in: query + name: sortOrder + description: Order in which the sortBy param is applied. Allowed values are "ascending" and "descending". + post: + tags: + - Configuration – User Management + summary: Create new User + description: Create new User + operationId: post-user + requestBody: + content: + application/json: + schema: + title: User Details. + description: User Details. + $ref: '#/components/schemas/ExtendedCustomUser' + responses: + '201': + description: Created + content: + application/json: + schema: + title: User Details. + $ref: '#/components/schemas/CustomUser' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/user.write] + put: + tags: + - Configuration – User Management + summary: Update User. + description: Update User. + operationId: put-user + requestBody: + content: + application/json: + schema: + title: User Details. + $ref: '#/components/schemas/CustomUser' + responses: + '200': + description: OK + content: + application/json: + schema: + title: User Details. + $ref: '#/components/schemas/CustomUser' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/user.write] + /jans-config-api/mgt/configuser/{inum}: + parameters: + - schema: + type: string + name: inum + in: path + description: User identifier + required: true + get: + tags: + - Configuration – User Management + summary: Get User by Inum + description: Get User by Inum. + operationId: get-user-by-inum + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CustomUser' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/user.readonly] + delete: + tags: + - Configuration – User Management + summary: Delete User. + description: Delete User. + operationId: delete-user + responses: + '204': + description: No Content + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/user.delete] + patch: + tags: + - Configuration – User Management + summary: Patch user properties by Inum. + description: Patch user properties by Inum. + operationId: patch-user-by-inum + requestBody: + content: + application/json: + schema: + type: object + $ref: '#/components/schemas/UserPatchRequest' + description: Patch request object + example: '[ {"jsonPatchString": {"op": "add", "path": "userId","value": "test-user" }, "customAttributes": [{"name": "name, displayName, birthdate, email","multiValued": true,"values": ["string"]}]}]' + responses: + '200': + description: OK + content: + application/json: + schema: + title: User Details. + $ref: '#/components/schemas/CustomUser' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + description: Internal Server Error + security: + - oauth2: [https://jans.io/oauth/config/user.write] + + + /jans-config-api/scim/config: + get: + summary: Retrieves SCIM App configuration. + description: Retrieves SCIM configuration. + operationId: get-scim-config + security: + - oauth2: [https://jans.io/scim/config.readonly] + tags: + - SCIM - Config Management + x-cli-plugin: scim + responses: + '200': + description: OK + content: + application/json: + schema: + title: ScimAppConfiguration + description: SCIM App configuration. + $ref: '#/components/schemas/ScimAppConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + patch: + summary: Partially modifies SCIM App configuration. + description: Partially modifies SCIM App configuration. + operationId: patch-scim-config + security: + - oauth2: [https://jans.io/scim/config.write] + tags: + - SCIM - Config Management + x-cli-plugin: scim + requestBody: + content: + application/json-patch+json: + schema: + type: array + items: + $ref: '#/components/schemas/PatchRequest' + description: String representing patch-document. + example: '[ {op:replace, path: loggingLevel, value: DEBUG } ]' + responses: + '200': + description: OK + content: + application/json: + schema: + title: ScimAppConfiguration + description: SCIM App configuration. + $ref: '#/components/schemas/ScimAppConfiguration' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + + /jans-config-api/api/v1/org: + get: + summary: Retrieves organization configuration. + description: Retrieves organization configuration. + operationId: get-organization-config + security: + - oauth2: [https://jans.io/oauth/config/organization.readonly] + tags: + - Organization Configuration + responses: + '200': + description: OK + content: + application/json: + schema: + title: Organization + description: Organization configuration. + $ref: '#/components/schemas/Organization' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + patch: + summary: Partially modifies organization configuration. + description: Partially modifies organization configuration. + operationId: patch-organization-config + security: + - oauth2: [https://jans.io/oauth/config/organization.write] + tags: + - Organization Configuration + requestBody: + content: + application/json-patch+json: + schema: + type: array + items: + $ref: '#/components/schemas/PatchRequest' + description: String representing patch-document. + example: '[{"op": "add", "path": "/jsFaviconPath", "value": "/opt/jans/jetty/jans-auth/custom/static/"}]' + responses: + '200': + description: OK + content: + application/json: + schema: + title: Organization + description: Organization configuration. + $ref: '#/components/schemas/Organization' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + + /jans-config-api/api/v1/jans-auth-server/health: + get: + summary: Returns auth server health status. + description: Returns auth server health status. + operationId: get-auth-server-health + tags: + - Auth Server Health - Check + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/AuthHealthStatus' + '500': + description: Internal Server Error + + /jans-config-api/admin-ui/user/roles: + get: + tags: + - Admin UI - Role + x-cli-plugin: admin-ui + summary: Get all admin ui roles. + description: Get all admin ui roles. + operationId: get-adminui-roles + security: + - oauth2: [https://jans.io/oauth/jans-auth-server/config/adminui/user/role.read] + responses: + '200': + description: OK + content: + application/json: + schema: + title: Get admin ui roles. + description: Get admin ui roles. + type: array + items: + $ref: '#/components/schemas/AdminRole' + '400': + $ref: '#/components/responses/NotAcceptable' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + post: + tags: + - Admin UI - Role + x-cli-plugin: admin-ui + summary: Add admin ui role. + description: Add admin ui role. + operationId: add-adminui-role + security: + - oauth2: [https://jans.io/oauth/jans-auth-server/config/adminui/user/role.write] + requestBody: + content: + application/json: + schema: + required: + - role + $ref: '#/components/schemas/AdminRole' + responses: + '200': + description: OK + content: + application/json: + schema: + title: Add admin ui role. + description: Add admin ui role. + type: array + items: + $ref: '#/components/schemas/AdminRole' + '400': + $ref: '#/components/responses/NotAcceptable' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + put: + tags: + - Admin UI - Role + x-cli-plugin: admin-ui + summary: Edit admin ui role. + description: Edit admin ui role. + operationId: edit-adminui-role + security: + - oauth2: [https://jans.io/oauth/jans-auth-server/config/adminui/user/role.write] + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AdminRole' + responses: + '200': + description: OK + content: + application/json: + schema: + title: Edit admin ui role. + description: Edit admin ui role. + type: array + items: + $ref: '#/components/schemas/AdminRole' + '400': + $ref: '#/components/responses/NotAcceptable' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + delete: + tags: + - Admin UI - Role + x-cli-plugin: admin-ui + summary: Delete admin ui role. + description: Delete admin ui role. + operationId: delete-adminui-role + security: + - oauth2: [https://jans.io/oauth/jans-auth-server/config/adminui/user/role.write] + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AdminRole' + responses: + '200': + description: OK + content: + application/json: + schema: + title: Delete admin ui role. + description: Delete admin ui role. + type: array + items: + $ref: '#/components/schemas/AdminRole' + '400': + $ref: '#/components/responses/NotAcceptable' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + /jans-config-api/admin-ui/user/permissions: + get: + tags: + - Admin UI - Permission + x-cli-plugin: admin-ui + summary: Get admin ui permissions. + description: Get admin ui permissions. + operationId: get-adminui-permissions + security: + - oauth2: [https://jans.io/oauth/jans-auth-server/config/adminui/user/permission.read] + responses: + '200': + description: OK + content: + application/json: + schema: + title: Get admin ui permissions. + description: Get admin ui permissions. + type: array + items: + $ref: '#/components/schemas/AdminPermission' + '400': + $ref: '#/components/responses/NotAcceptable' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + post: + tags: + - Admin UI - Permission + x-cli-plugin: admin-ui + summary: Add admin ui permission. + description: Add admin ui permission. + operationId: add-adminui-permission + security: + - oauth2: [https://jans.io/oauth/jans-auth-server/config/adminui/user/permission.write] + requestBody: + content: + application/json: + schema: + required: + - permission + $ref: '#/components/schemas/AdminPermission' + responses: + '200': + description: OK + content: + application/json: + schema: + title: Add admin ui permission. + description: Add admin ui permission. + type: array + items: + $ref: '#/components/schemas/AdminPermission' + '400': + $ref: '#/components/responses/NotAcceptable' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + put: + tags: + - Admin UI - Permission + x-cli-plugin: admin-ui + summary: Edit admin ui permission. + description: Edit admin ui permission. + operationId: edit-adminui-permission + security: + - oauth2: [https://jans.io/oauth/jans-auth-server/config/adminui/user/permission.write] + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AdminPermission' + responses: + '200': + description: OK + content: + application/json: + schema: + title: Edit admin ui permission. + description: Edit admin ui permission. + type: array + items: + $ref: '#/components/schemas/AdminPermission' + '400': + $ref: '#/components/responses/NotAcceptable' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + delete: + tags: + - Admin UI - Permission + x-cli-plugin: admin-ui + summary: Delete admin ui permission. + description: Delete admin ui permission. + operationId: delete-adminui-permission + security: + - oauth2: [https://jans.io/oauth/jans-auth-server/config/adminui/user/permission.write] + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AdminPermission' + responses: + '200': + description: OK + content: + application/json: + schema: + title: Delete admin ui permission. + description: Delete admin ui permission. + type: array + items: + $ref: '#/components/schemas/AdminPermission' + '400': + $ref: '#/components/responses/NotAcceptable' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + /jans-config-api/admin-ui/user/rolePermissionsMapping: + get: + tags: + - Admin UI - Role-Permissions Mapping + x-cli-plugin: admin-ui + summary: Get admin ui role-permissions mapping. + description: Get admin ui role-permissions mapping. + operationId: get-adminui-role-permissions + security: + - oauth2: [https://jans.io/oauth/jans-auth-server/config/adminui/user/rolePermissionMapping.readonly] + responses: + '200': + description: OK + content: + application/json: + schema: + title: Get admin ui role-permissions mapping. + description: Get admin ui role-permissions mapping. + type: array + items: + $ref: '#/components/schemas/RolePermissionMapping' + '400': + $ref: '#/components/responses/NotAcceptable' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + post: + tags: + - Admin UI - Role-Permissions Mapping + x-cli-plugin: admin-ui + summary: Add role-permissions mapping. + description: Add role-permissions mapping. + operationId: Add role-permissions mapping. + security: + - oauth2: [https://jans.io/oauth/jans-auth-server/config/adminui/user/rolePermissionMapping.write] + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RolePermissionMapping' + responses: + '200': + description: OK + content: + application/json: + schema: + title: Add role-permissions mapping. + description: Add role-permissions mapping. + type: array + items: + $ref: '#/components/schemas/RolePermissionMapping' + '400': + $ref: '#/components/responses/NotAcceptable' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + put: + tags: + - Admin UI - Role-Permissions Mapping + x-cli-plugin: admin-ui + summary: Map permissions to role. + description: Map permissions to role. + operationId: map-permissions-to-role + security: + - oauth2: [https://jans.io/oauth/jans-auth-server/config/adminui/user/rolePermissionMapping.write] + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RolePermissionMapping' + responses: + '200': + description: OK + content: + application/json: + schema: + title: Map permissions to role. + description: Map permissions to role. + type: array + items: + $ref: '#/components/schemas/RolePermissionMapping' + '400': + $ref: '#/components/responses/NotAcceptable' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + delete: + tags: + - Admin UI - Role-Permissions Mapping + x-cli-plugin: admin-ui + summary: Remove role-permissions mapping. + description: Remove role-permissions mapping. + operationId: remove-role-permissions-permission + security: + - oauth2: [https://jans.io/oauth/jans-auth-server/config/adminui/user/rolePermissionMapping.write] + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RolePermissionMapping' + responses: + '200': + description: OK + content: + application/json: + schema: + title: Remove role-permissions mapping. + description: Remove role-permissions mapping. + type: array + items: + $ref: '#/components/schemas/RolePermissionMapping' + '400': + $ref: '#/components/responses/NotAcceptable' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + /jans-config-api/admin-ui/license/isActive: + get: + tags: + - Admin UI - License + x-cli-plugin: admin-ui + summary: Check if admin-ui license is active. + description: Check if admin-ui license is active. + operationId: is-license-active + security: + - oauth2: [https://jans.io/oauth/jans-auth-server/config/adminui/license.readonly] + responses: + '200': + description: OK + content: + application/json: + schema: + title: Check if admin-ui license is active. + description: Check if admin-ui license is active. + $ref: '#/components/schemas/LicenseApiResponse' + '400': + $ref: '#/components/schemas/LicenseApiResponse' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/schemas/LicenseApiResponse' + /jans-config-api/admin-ui/license/activateLicense: + post: + tags: + - Admin UI - License + x-cli-plugin: admin-ui + summary: Activate license using license-key. + description: Activate license using license-key. + operationId: activate-adminui-license + security: + - oauth2: [https://jans.io/oauth/jans-auth-server/config/adminui/license.write] + requestBody: + content: + application/json: + schema: + required: + - licenseKey + $ref: '#/components/schemas/LicenseApiRequest' + responses: + '200': + description: OK + content: + application/json: + schema: + title: Activate license using license-key. + description: Activate license using license-key. + $ref: '#/components/schemas/LicenseApiResponse' + '400': + $ref: '#/components/schemas/LicenseApiResponse' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/schemas/LicenseApiResponse' + /jans-config-api/admin-ui/license/saveApiCredentials: + post: + tags: + - Admin UI - License + x-cli-plugin: admin-ui + summary: Save license api credentials. + description: Save license api credentials. + operationId: save-license-api-credentials + security: + - oauth2: [https://jans.io/oauth/jans-auth-server/config/adminui/license.write] + requestBody: + content: + application/json: + schema: + required: + - licenseKey + $ref: '#/components/schemas/LicenseSpringCredentials' + responses: + '200': + description: OK + content: + application/json: + schema: + title: Save license api credentials. + description: Save license api credentials. + $ref: '#/components/schemas/LicenseApiResponse' + '400': + $ref: '#/components/schemas/LicenseApiResponse' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/schemas/LicenseApiResponse' + /jans-config-api/admin-ui/license/licenseDetails: + get: + tags: + - Admin UI - License + x-cli-plugin: admin-ui + summary: Get admin ui license details. + description: Get admin ui license details. + operationId: get-adminui-license + security: + - oauth2: [https://jans.io/oauth/jans-auth-server/config/adminui/license.readonly] + responses: + '200': + description: OK + content: + application/json: + schema: + title: Get admin ui license details. + description: Get admin ui license details. + $ref: '#/components/schemas/LicenseDetailsResponse' + '400': + $ref: '#/components/responses/NotAcceptable' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + put: + tags: + - Admin UI - License + x-cli-plugin: admin-ui + summary: Edit admin ui license details. + description: Edit admin ui license details. + operationId: edit-adminui-license + security: + - oauth2: [https://jans.io/oauth/jans-auth-server/config/adminui/license.write] + requestBody: + content: + application/json: + schema: + required: + - role + $ref: '#/components/schemas/LicenseDetailsRequest' + responses: + '200': + description: OK + content: + application/json: + schema: + title: Edit admin ui license details. + description: Edit admin ui license details. + $ref: '#/components/schemas/LicenseDetailsResponse' + '400': + $ref: '#/components/responses/NotAcceptable' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' +components: + securitySchemes: + oauth2: + type: oauth2 + description: Authorization + flows: + clientCredentials: + tokenUrl: 'https://{op-hostname}/.../token' + scopes: + https://jans.io/oauth/jans-auth-server/config/properties.readonly: View Auth Server properties related information + https://jans.io/oauth/jans-auth-server/config/properties.write: Manage Auth Server properties related information + https://jans.io/oauth/config/fido2.readonly: View FIDO2 related information + https://jans.io/oauth/config/fido2.write: Manage FIDO2 related information + https://jans.io/oauth/config/attributes.readonly: View attribute related information + https://jans.io/oauth/config/attributes.write: Manage attribute related information + https://jans.io/oauth/config/attributes.delete: Delete attribute related information + https://jans.io/oauth/config/acrs.readonly: View ACRS related information + https://jans.io/oauth/config/acrs.write: Manage ACRS related information + https://jans.io/oauth/config/database/ldap.readonly: View LDAP database related information + https://jans.io/oauth/config/database/ldap.write: Manage LDAP database related information + https://jans.io/oauth/config/database/ldap.delete: Delete LDAP database related information + https://jans.io/oauth/config/database/couchbase.readonly: View Couchbase database information + https://jans.io/oauth/config/database/couchbase.write: Manage Couchbase database related information + https://jans.io/oauth/config/database/couchbase.delete: Delete Couchbase database related information + https://jans.io/oauth/config/scripts.readonly: View cache scripts information + https://jans.io/oauth/config/scripts.write: Manage scripts related information + https://jans.io/oauth/config/scripts.delete: Delete scripts related information + https://jans.io/oauth/config/cache.readonly: View cache related information + https://jans.io/oauth/config/cache.write: Manage cache related information + https://jans.io/oauth/config/smtp.readonly: View SMTP related information + https://jans.io/oauth/config/smtp.write: Manage SMTP related information + https://jans.io/oauth/config/smtp.delete: Delete SMTP related information + https://jans.io/oauth/config/logging.readonly: View logging related information + https://jans.io/oauth/config/logging.write: Manage logging related information + https://jans.io/oauth/config/jwks.readonly: View JWKS related information + https://jans.io/oauth/config/jwks.write: Manage JWKS related information + https://jans.io/oauth/config/openid/clients.readonly: View clients related information + https://jans.io/oauth/config/openid/clients.write: Manage clients related information + https://jans.io/oauth/config/openid/clients.delete: Delete clients related information + https://jans.io/oauth/config/scopes.readonly: View scope related information + https://jans.io/oauth/config/scopes.write: Manage scope related information + https://jans.io/oauth/config/scopes.delete: Delete scope related information + https://jans.io/oauth/config/uma/resources.readonly: View UMA Resource related information + https://jans.io/oauth/config/uma/resources.write: Manage UMA Resource related information + https://jans.io/oauth/config/uma/resources.delete: Delete UMA Resource related information + https://jans.io/oauth/config/database/sql.readonly: View SQL database related information + https://jans.io/oauth/config/database/sql.write: Manage SQL database related information + https://jans.io/oauth/config/database/sql.delete: Delete SQL database related information + https://jans.io/oauth/config/stats.readonly: Vew server with basic statistic + https://jans.io/oauth/config/scim/users.read: Vew scim user related information + https://jans.io/oauth/config/scim/users.write: Manage scim user related information + https://jans.io/scim/config.readonly: Vew SCIM App configuration + https://jans.io/scim/config.write: Manage SCIM App configuration + https://jans.io/oauth/config/organization.readonly: View organization configuration information + https://jans.io/oauth/config/organization.write: Manage organization configuration information + https://jans.io/oauth/config/user.readonly: View user related information + https://jans.io/oauth/config/user.write: Manage user related information + https://jans.io/oauth/config/user.delete: Delete user related information + + + responses: + Found: + description: Resource Found. + content: {} + InvalidRequest: + description: Invalid parameters are provided to endpoint. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + Unauthorized: + description: Access token is missing or invalid. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + AccessDenied: + description: Invalid details provided hence access denied. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + NotFound: + description: Resource Not Found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + NotAcceptable: + description: Request Not Acceptable. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + InternalServerError: + description: Internal error occurred. Please check log file for details. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + schemas: + ErrorResponse: + required: + - error_code + - error_description + type: object + properties: + error_code: + type: string + error_description: + type: string + details: + type: string + CustomScript: + type: object + description: Script + required: + - name + - script + - scriptType + - programmingLanguage + - moduleProperties + - level + properties: + dn: + type: string + inum: + description: XRI i-number. Identifier to uniquely identify the script. + type: string + name: + type: string + pattern: '^[a-zA-Z0-9_\\-\\:\\/\\.]+$' + minLength: 1 + maxLength: 60 + description: 'Custom script name. Should contain only letters, digits and underscores.' + aliases: + type: array + description: List of possible aliases for the custom script. + items: + type: string + description: + type: string + description: Details describing the script. + script: + type: string + description: Actual script. + scriptType: + type: string + description: Type of script. + enum: + - PERSON_AUTHENTICATION + - INTROSPECTION + - RESOURCE_OWNER_PASSWORD_CREDENTIALS + - APPLICATION_SESSION + - CACHE_REFRESH + - CLIENT_REGISTRATION + - ID_GENERATOR + - UMA_RPT_POLICY + - UMA_RPT_CLAIMS + - UMA_CLAIMS_GATHERING + - CONSENT_GATHERING + - DYNAMIC_SCOPE + - SPONTANEOUS_SCOPE + - END_SESSION + - POST_AUTHN + - SCIM + - CIBA_END_USER_NOTIFICATION + - REVOKE_TOKEN + - PERSISTENCE_EXTENSION + - IDP + - DISCOVERY + - UPDATE_TOKEN + - CONFIG_API + programmingLanguage: + type: string + enum: + - PYTHON + - JAVA + description: Programming language of the custom script. + moduleProperties: + type: array + description: Module-level properties applicable to the script. + items: + $ref: '#/components/schemas/SimpleCustomProperty' + configurationProperties: + type: array + description: Configuration properties applicable to the script. + items: + $ref: '#/components/schemas/SimpleExtendedCustomProperty' + level: + type: integer + description: Script level. + revision: + type: integer + format: int64 + description: Update revision number of the script. + default: 0 + enabled: + type: boolean + description: boolean value indicating if script enabled. + default: false + scriptError: + type: object + description: Possible errors assosiated with the script. + $ref: '#/components/schemas/ScriptError' + modified: + type: boolean + description: boolean value indicating if the script is modified. + default: false + internal: + type: boolean + description: boolean value indicating if the script is internal. + default: false + + LdapConfiguration: + type: object + required: + - configId + - bindDN + - maxConnections + - primaryKey + - localPrimaryKey + - bindPassword + - servers + - baseDNs + - useSSL + properties: + configId: + type: string + description: Unique identifier - Name + example: auth_ldap_server + bindDN: + type: string + description: This contains the username to connect to the backend server. You need to use full DN here. As for example, cn=jans,dc=company,dc=org. + bindPassword: + type: string + description: Ldap password for binding. + servers: + type: array + description: List of LDAP authentication servers. + items: + type: string + description: Unique name of the authentication server and port number. + example: 'authserver.org:63' + maxConnections: + type: integer + description: This value defines the maximum number of connections that are allowed to read the backend Active Directory/LDAP server. + format: int32 + default: 2 + useSSL: + type: boolean + description: Enable SSL communication between Jans Server and LDAP server. + baseDNs: + type: array + description: List contains the location of the Active Directory/LDAP tree from where the Gluu Server shall read the user information. + items: + type: string + primaryKey: + type: string + description: Used to search and bind operations in configured LDAP server. + example: 'SAMAccountName,uid, email' + localPrimaryKey: + type: string + description: Used to search local user entry in Gluu Server’s internal LDAP directory. + example: 'uid, email' + useAnonymousBind: + type: boolean + description: Boolean value used to indicate if the LDAP Server will allow anonymous bind request. + default: false + enabled: + type: boolean + description: Boolean value used to indicate if the LDAP Server is enabled. Do not use this unless the server administrator has entered all the required values. + default: false + version: + type: integer + description: LDAP server version. + level: + type: integer + description: A string that indicates the level. + + CouchbaseConfiguration: + type: object + required: + - configId + - userName + - userPassword + - servers + - defaultBucket + - buckets + - passwordEncryptionMethod + - sslTrustStoreFile + - sslTrustStorePin + - sslTrustStoreFormat + properties: + configId: + type: string + description: Unique identifier + userName: + type: string + description: Couchbase server user. + userPassword: + type: string + description: Encoded Couchbase server user password. + servers: + type: array + items: + type: string + description: Couchbase server host and port. + defaultBucket: + type: string + description: Main bucket that application should use if other mapping rules were not applied. + buckets: + type: array + items: + type: string + description: List of buckets defining mapping rules. + passwordEncryptionMethod: + type: string + description: A list of the password encryption algorithms. + enum: + - SHA + - SSHA + - SHA-256 + - SSHA-256 + - SHA-384 + - SSHA-384 + - SHA-512 + - SSHA-512 + - MD5 + - SMD5 + - CRYPT + - CRYPT-MD5 + - CRYPT-SHA-256 + - CRYPT-SHA-512 + - CRYPT-BCRYPT + - CRYPT-BCRYPT + - PKCS5S2 + operationTracingEnabled: + type: boolean + description: Boolean value True if tracing is enabled on the environment. + default: false + mutationTokensEnabled: + type: boolean + description: If mutation tokens are enabled, they can be used for advanced durability requirements, as well as optimized RYOW consistency. + connectTimeout: + type: integer + description: The default timeout for connection timeout. + format: int32 + computationPoolSize: + type: integer + format: int32 + description: Sets the pool size (number of threads to use) for all non-blocking operations, default value is the number of CPUs. + useSSL: + type: boolean + description: Identifies if SSL should be enabled. + default: true + sslTrustStoreFile: + type: string + description: The path to the trust store file to use. It contains the trusted certificates. + sslTrustStorePin: + type: string + description: The PIN to use to access the contents of the trust store. + sslTrustStoreFormat: + type: string + description: The format to use for the trust store. + binaryAttributes: + type: array + description: List of binary attributes. + items: + type: string + certificateAttributes: + type: array + description: List of certificate attributes. + items: + type: string + + SqlConfiguration: + type: object + required: + - configId + - userName + - userPassword + - connectionUri + - schemaName + - passwordEncryptionMethod + properties: + configId: + type: string + description: Unique identifier + userName: + type: string + description: Sql server user. + userPassword: + type: string + description: Encoded Sql server user password. + connectionUri: + type: array + items: + type: string + description: Sql server connection Uri. + schemaName: + type: string + description: Database schema name. + passwordEncryptionMethod: + type: string + description: A list of the password encryption algorithms. + enum: + - SHA + - SSHA + - SHA-256 + - SSHA-256 + - SHA-384 + - SSHA-384 + - SHA-512 + - SSHA-512 + - MD5 + - SMD5 + - CRYPT + - CRYPT-MD5 + - CRYPT-SHA-256 + - CRYPT-SHA-512 + - CRYPT-BCRYPT + - CRYPT-BCRYPT + - PKCS5S2 + serverTimezone: + type: string + description: Database schema name. + binaryAttributes: + type: array + description: List of binary attributes. + items: + type: string + certificateAttributes: + type: array + description: List of certificate attributes. + items: + type: string + JsonWebKey: + type: object + description: JsonWebKey + required: + - kid + - kty + - use + - alg + - exp + properties: + name: + type: string + description: Name of the key. + descr: + type: string + description: key description. + kid: + type: string + description: The unique identifier for the key. + kty: + type: string + description: The family of cryptographic algorithms used with the key. + use: + type: string + description: How the key was meant to be used; sig represents the signature. + alg: + type: string + description: The specific cryptographic algorithm used with the key. + crv: + type: string + description: The crv member identifies the cryptographic curve used with the key. Values defined by this specification are P-256, P-384 and P-521. Additional crv values MAY be used, provided they are understood by implementations using that Elliptic Curve key. The crv value is case sensitive. + exp: + type: integer + format: int64 + description: Contains the token expiration timestamp + x5c: + type: array + description: The x.509 certificate chain. The first entry in the array is the certificate to use for token verification; the other certificates can be used to verify this first certificate. + items: + type: string + n: + type: string + description: The modulus for the RSA public key. + e: + type: string + description: The exponent for the RSA public key. + x: + type: string + description: The x member contains the x coordinate for the elliptic curve point. It is represented as the base64url encoding of the coordinate's big endian representation. + y: + type: string + description: The y member contains the y coordinate for the elliptic curve point. It is represented as the base64url encoding of the coordinate's big endian representation. + + PersistenceConfiguration: + title: PersistenceConfiguration + description: Persistence configuration properties. + properties: + persistenceType: + type: string + description: Jans Auth Server persistence type configured. + enum: + - ldap + - couchbase + - sql + - spanner + - hybrid + + AppConfiguration: + title: AppConfiguration + description: App configuration properties. + properties: + issuer: + type: string + description: URL using the https scheme that OP asserts as Issuer identifier. + example: 'https://server.example.com/' + baseEndpoint: + type: string + description: The base URL for endpoints. + example: 'https://server.example.com/restv1' + authorizationEndpoint: + type: string + description: The authorization endpoint URL. + example: 'https://server.example.com/restv1/authorize' + tokenEndpoint: + type: string + description: The token endpoint URL. + example: 'https://server.example.com/restv1/token' + tokenRevocationEndpoint: + type: string + description: The URL for the access_token or refresh_token revocation endpoint. + example: 'https://server.example.com/restv1/revoke' + userInfoEndpoint: + type: string + description: The User Info endpoint URL. + example: 'https://server.example.com/restv1/userinfo' + clientInfoEndpoint: + type: string + description: The Client Info endpoint URL. + example: 'https://server.example.com/restv1/clientinfo' + checkSessionIFrame: + type: string + description: URL for an OP IFrame that supports cross-origin communications for session state information with the RP Client using the HTML5 postMessage API. + example: 'https://server.example.com/opiframe.htm' + endSessionEndpoint: + type: string + description: URL at the OP to which an RP can perform a redirect to request that the end user be logged out at the OP. + example: 'https://server.example.com/restv1/end_session' + jwksUri: + type: string + description: URL of the OP\'s JSON Web Key Set (JWK) document. This contains the signing key(s) the RP uses to validate signatures from the OP. + example: 'https://server.example.com/restv1/jwks' + registrationEndpoint: + type: string + description: URL of the Registration Endpoint. + example: 'https://server.example.com/restv1/register' + openIdDiscoveryEndpoint: + type: string + description: URL for the Discovery Endpoint. + example: 'https://server.example.com/.well-known/webfinger' + openIdConfigurationEndpoint: + type: string + description: URL for the Open ID Connect Configuration Endpoint. + example: 'https://server.example.com/.well-known/openid-configuration' + idGenerationEndpoint: + type: string + description: URL for the ID Generation Endpoint. + example: 'https://server.example.com/restv1/id' + introspectionEndpoint: + type: string + description: URL for the Introspection Endpoint. + example: 'https://server.example.com/restv1/introspection' + deviceAuthzEndpoint: + type: string + description: URL for the Device Authorization. + example: 'https://server.example.com/restv1/device_authorization' + sessionAsJwt: + type: boolean + description: Boolean value true saves session data as a JWT. + sectorIdentifierCacheLifetimeInMinutes: + type: integer + description: Sector Identifier cache lifetime in minutes. + umaConfigurationEndpoint: + type: string + description: URL for the UMA Configuration Endpoint. + example: 'https://server.example.com/restv1/uma2-configuration' + umaRptAsJwt: + type: boolean + description: Issue RPT as JWT or as random string. + umaRptLifetime: + type: integer + description: UMA RPT lifetime. + umaTicketLifetime: + type: integer + description: UMA ticket lifetime. + umaPctLifetime: + type: integer + description: UMA PCT lifetime. + umaResourceLifetime: + type: integer + description: UMA PCT lifetime. + umaAddScopesAutomatically: + type: boolean + description: Add UMA scopes automatically if it is not registered yet. + umaValidateClaimToken: + type: boolean + description: Validate claim_token as id_token assuming it is issued by local idp. + umaGrantAccessIfNoPolicies: + type: boolean + description: Specifies whether to grant access to resources if there are no any policies associated with scopes. + umaRestrictResourceToAssociatedClient: + type: boolean + description: Restrict access to resource by associated client. + spontaneousScopeLifetime: + type: integer + description: The lifetime of spontaneous scope in seconds. + openidSubAttribute: + type: string + description: Specifies which LDAP attribute is used for the subject identifier claim. + example: inum + responseTypesSupported: + type: array + description: A list of the OAuth 2.0 response_type values that this OP supports. + items: + type: string + enum: + - code + - token + - id_token + responseModesSupported: + type: array + description: A list of the OAuth 2.0 Response Mode values that this OP supports. + items: + type: string + enum: + - query + - fragment + - form_post + grantTypesSupported: + type: array + description: A list of the OAuth 2.0 Grant Type values that this OP supports. + items: + type: string + enum: + - authorization_code + - implicit + - password + - client_credentials + - refresh_token + - '\urn\:ietf\:params\:oauth\:grant-type\:uma-ticket' + - '\urn\:openid\:params\:grant-type\:ciba' + subjectTypesSupported: + type: array + description: A list of the Subject Identifier types that this OP supports. Valid types include pairwise and public. + items: + type: string + enum: + - public + - pairwise + defaultSubjectType: + type: string + description: Default Subject Type used for Dynamic Client Registration. + enum: + - public + - pairwise + userInfoSigningAlgValuesSupported: + type: array + description: A list of the JWS signing algorithms (alg values) JWA supported by the UserInfo Endpoint to encode the Claims in a JWT. + items: + type: string + enum: + - HS256 + - HS384 + - HS512 + - RS256 + - RS384 + - RS512 + - ES256 + - ES384 + - ES512 + - PS256 + - PS384 + - PS512 + userInfoEncryptionAlgValuesSupported: + type: array + description: A list of the JWE encryption algorithms (alg values) JWA supported by the UserInfo Endpoint to encode the Claims in a JWT. + items: + type: string + enum: + - RSA1_5 + - RSA-OAEP + - A128KW + - A256KW + userInfoEncryptionEncValuesSupported: + type: array + description: A list of the JWE encryption algorithms (enc values) JWA supported by the UserInfo Endpoint to encode the Claims in a JWT. + items: + type: string + enum: + - A128CBC+HS256 + - A256CBC+HS512 + - A128GCM + - A256GCM + idTokenSigningAlgValuesSupported: + type: array + description: A list of the JWS signing algorithms (alg values) supported by the OP for the ID Token to encode the Claims in a JWT. + items: + type: string + enum: + - none + - HS256 + - HS384 + - HS512 + - RS256 + - RS384 + - RS512 + - ES256 + - ES384 + - ES512 + - PS256 + - PS384 + - PS512 + idTokenEncryptionAlgValuesSupported: + type: array + description: A list of the JWE encryption algorithms (alg values) supported by the OP for the ID Token to encode the Claims in a JWT. + items: + type: string + enum: + - RSA1_5 + - RSA-OAEP + - A128KW + - A256KW + idTokenEncryptionEncValuesSupported: + type: array + description: A list of the JWE encryption algorithms (enc values) supported by the OP for the ID Token to encode the Claims in a JWT. + items: + type: string + enum: + - A128CBC+HS256 + - A256CBC+HS512 + - A128GCM + - A256GCM + requestObjectSigningAlgValuesSupported: + type: array + description: A list of the JWS signing algorithms (alg values) supported by the OP for Request Objects. + items: + type: string + enum: + - none + - HS256 + - HS384 + - HS512 + - RS256 + - RS384 + - RS512 + - ES256 + - ES384 + - ES512 + - PS256 + - PS384 + - PS512 + requestObjectEncryptionAlgValuesSupported: + type: array + description: A list of the JWE encryption algorithms (alg values) supported by the OP for Request Objects. + items: + type: string + enum: + - RSA1_5 + - RSA-OAEP + - A128KW + - A256KW + requestObjectEncryptionEncValuesSupported: + type: array + description: A list of the JWE encryption algorithms (enc values) supported by the OP for Request Objects. + items: + type: string + enum: + - A128CBC+HS256 + - A256CBC+HS512 + - A128GCM + - A256GCM + tokenEndpointAuthMethodsSupported: + type: array + description: A list of Client Authentication methods supported by this Token Endpoint. + items: + type: string + enum: + - client_secret_basic + - client_secret_post + - client_secret_jwt + - private_key_jwt + tokenEndpointAuthSigningAlgValuesSupported: + type: array + description: A list of the JWS signing algorithms (alg values) supported by the Token Endpoint for the signature on the JWT used to authenticate the Client at the Token Endpoint for the private_key_jwt and client_secret_jwt authentication methods. + items: + type: string + enum: + - HS256 + - HS384 + - HS512 + - RS256 + - RS384 + - RS512 + - ES256 + - ES384 + - ES512 + - PS256 + - PS384 + - PS512 + dynamicRegistrationCustomAttributes: + type: array + description: Custom attributes for the Dynamic registration. + items: + type: string + enum: + - jansTrustedClnt + displayValuesSupported: + type: array + description: A list of the display parameter values that the OpenID Provider supports. + items: + type: string + enum: + - page + - popup + claimTypesSupported: + type: array + description: A list of the Claim Types that the OpenID Provider supports. + items: + type: string + enum: + - normal + jwksAlgorithmsSupported: + type: array + description: A list of algorithms that will be used in JWKS endpoint. + items: + type: string + enum: + - RS256 + - RS384 + - RS512 + - ES256 + - ES384 + - ES512 + - PS256 + - PS384 + - PS512 + - RSA1_5 + - RSA-OAEP + serviceDocumentation: + type: string + description: URL of a page containing human-readable information that developers might want or need to know when using the OpenID Provider. + format: url + example: 'http://gluu.org/docs' + claimsLocalesSupported: + type: array + description: Languages and scripts supported for values in Claims being returned. + items: + type: string + enum: + - en + idTokenTokenBindingCnfValuesSupported: + type: array + description: Array containing a list of the JWT Confirmation Method member names supported by the OP for Token Binding of ID Tokens. The presence of this parameter indicates that the OpenID Provider supports Token Binding of ID Tokens. If omitted, the default is that the OpenID Provider does not support Token Binding of ID Tokens. + items: + type: string + enum: + - tbh + uiLocalesSupported: + type: array + description: Languages and scripts supported for the user interface. + items: + type: string + enum: + - en + - es + claimsParameterSupported: + type: boolean + description: Specifies whether the OP supports use of the claim’s parameter. + requestParameterSupported: + type: boolean + description: Boolean value specifying whether the OP supports use of the request parameter. + requestUriParameterSupported: + type: boolean + description: Boolean value specifying whether the OP supports use of the request_uri parameter. + requestUriBlockList: + type: array + description: Block list for requestUri that can come to Authorization Endpoint (e.g. "localhost") + items: + type: string + requestUriHashVerificationEnabled: + type: boolean + description: Boolean value specifying whether the OP supports use of the request_uri hash verification. + requireRequestUriRegistration: + type: boolean + description: Boolean value specifying whether the OP requires any request_uri values used to be pre-registered using the request_uris registration parameter. + opPolicyUri: + type: string + description: URL that the OpenID Provider provides to the person registering the Client to read about the OP\'s requirements on how the Relying Party can use the data provided by the OP. + example: 'http://ox.gluu.org/doku.php?id=jans:policy' + opTosUri: + type: string + description: URL that the OpenID Provider provides to the person registering the Client to read about OpenID Provider's terms of service. + example: 'http://ox.gluu.org/doku.php?id=jans:tos' + authorizationCodeLifetime: + type: integer + description: The lifetime of the Authorization Code. + refreshTokenLifetime: + type: integer + description: The lifetime of the Refresh Token. + idTokenLifetime: + type: integer + description: The lifetime of the ID Token. + example: 3600 + idTokenFilterClaimsBasedOnAccessToken: + type: boolean + description: Boolean value specifying whether idToken filters claims based on accessToken. + accessTokenLifetime: + type: integer + description: The lifetime of the short-lived Access Token. + example: 3600 + cleanServiceInterval: + type: integer + description: Time interval for the Clean Service in seconds. + example: 60 + cleanServiceBatchChunkSize: + type: integer + description: Each clean up iteration fetches chunk of expired data per base dn and removes it from storage. + example: 10000 + keyRegenerationEnabled: + type: boolean + description: Boolean value specifying whether to regenerate keys. + keyRegenerationInterval: + type: integer + description: The interval for key regeneration in hours. + example: 48 + defaultSignatureAlgorithm: + type: string + description: The default signature algorithm to sign ID Tokens. + enum: + - HS256 + - HS384 + - HS512 + - RS256 + - RS384 + - RS512 + - ES256 + - ES384 + - ES512 + oxOpenIdConnectVersion: + type: string + description: OpenID Connect Version. + example: openidconnect-1.0 + oxId: + type: string + description: URL for the Inum generator Service. + format: url + example: 'https://server.example.com/oxid/service/jans/inum' + dynamicRegistrationExpirationTime: + type: integer + description: Expiration time in seconds for clients created with dynamic registration, -1 means never expire. + example: -1 + dynamicRegistrationPersistClientAuthorizations: + type: boolean + description: Boolean value specifying whether to persist client authorizations. + trustedClientEnabled: + type: boolean + description: Boolean value specifying whether a client is trusted and no authorization is required. + skipAuthorizationForOpenIdScopeAndPairwiseId: + type: boolean + description: If a client has only openid scope and pairwise id, person should not have to authorize. + dynamicRegistrationScopesParamEnabled: + type: boolean + description: Boolean value specifying whether to enable scopes parameter in dynamic registration. + dynamicRegistrationPasswordGrantTypeEnabled: + type: boolean + description: Boolean value specifying whether to enable Password Grant Type during Dynamic Registration. + dynamicRegistrationAllowedPasswordGrantScopes: + type: array + description: List of grant scopes for dynamic registration. + items: + type: string + dynamicRegistrationCustomObjectClass: + type: string + description: LDAP custom object class for dynamic registration. + personCustomObjectClassList: + type: array + description: LDAP custom object class list for dynamic person enrolment. + items: + type: string + enum: + - gluuCustomPerson + - gluuPerson + persistIdTokenInLdap: + type: boolean + description: Specifies whether to persist id_token into LDAP (otherwise saves into cache). + persistRefreshTokenInLdap: + type: boolean + description: Specifies whether to persist refresh_token into LDAP (otherwise saves into cache). + allowPostLogoutRedirectWithoutValidation: + type: boolean + description: Allows post logout redirect without validation for End Session Endpoint. + invalidateSessionCookiesAfterAuthorizationFlow: + type: boolean + description: Boolean value to specify whether to invalidate `session_id` and `consent_session_id` cookies right after successful or unsuccessful authorization. + returnClientSecretOnRead: + type: boolean + description: Boolean value specifying whether a client_secret is returned on client GET or PUT. False value means not to return secret. + rejectJwtWithNoneAlg: + type: boolean + description: Boolean value specifying whether reject JWT requested or validated with algorithm None. + expirationNotificatorEnabled: + type: boolean + description: Boolean value specifying whether expiration notificator is enabled (used to identify expiration for persistence that support TTL, like Couchbase). + useNestedJwtDuringEncryption: + type: boolean + description: Boolean value specifying whether to use nested Jwt during encryption. + expirationNotificatorMapSizeLimit: + type: integer + description: The expiration notificator maximum size limit. + example: 100000 + expirationNotificatorIntervalInSeconds: + type: integer + description: The expiration notificator interval in seconds. + example: 600 + authenticationFiltersEnabled: + type: boolean + description: Boolean value specifying whether to enable user authentication filters. + clientAuthenticationFiltersEnabled: + type: boolean + description: Boolean value specifying whether to enable client authentication filters. + clientRegDefaultToCodeFlowWithRefresh: + type: boolean + description: Boolean value specifying whether to add Authorization Code Flow with Refresh grant during client registration. + authenticationFilters: + type: array + description: List of authentication filters. + items: + $ref: '#/components/schemas/AuthenticationFilters' + clientAuthenticationFilters: + type: array + description: List of client authentication filters. + items: + $ref: '#/components/schemas/AuthenticationFilters' + corsConfigurationFilters: + type: array + description: CORS Configuration filters. + items: + $ref: '#/components/schemas/CorsConfigurationFilter' + sessionIdUnusedLifetime: + type: integer + description: The lifetime for unused session states. + sessionIdUnauthenticatedUnusedLifetime: + type: integer + description: The lifetime for unused unauthenticated session states. + sessionIdEnabled: + type: boolean + description: Boolean value specifying whether to enable authentication by session_id. + sessionIdPersistOnPromptNone: + type: boolean + description: Boolean value specifying whether to persist session ID on prompt none. + sessionIdRequestParameterEnabled: + type: boolean + description: Boolean value specifying whether to enable session_id HTTP request parameter. + changeSessionIdOnAuthentication: + type: boolean + description: Boolean value specifying whether to change session_id on authentication. + sessionIdPersistInCache: + type: boolean + description: Boolean value specifying whether to persist session_id in cache. + sessionIdLifetime: + type: integer + description: The lifetime of session id in seconds. If 0 or -1 then expiration is not set. `session_id` cookie expires when browser session ends. + serverSessionIdLifetime: + type: integer + description: The sessionId lifetime in seconds for sessionId. By default same as sessionIdLifetime. + configurationUpdateInterval: + type: integer + description: The interval for configuration update in seconds. + enableClientGrantTypeUpdate: + type: boolean + description: Boolean value to specify if client can update Grant Type values. + dynamicGrantTypeDefault: + type: array + description: list of the OAuth 2.0 Grant Type values that it\'s possible to set via client registration API.. + items: + type: string + enum: + - none + - authorization_code + - implicit + - password + - client_credentials + - refresh_token + - 'urn:ietf:params:oauth:grant-type:uma-ticket' + - 'urn:openid:params:grant-type:ciba' + - 'urn:ietf:params:oauth:grant-type:device_code' + cssLocation: + type: string + description: The location for CSS files. + jsLocation: + type: string + description: The location for JavaScript files. + imgLocation: + type: string + description: The location for image files. + metricReporterInterval: + type: integer + description: The interval for metric reporter in seconds. + metricReporterKeepDataDays: + type: integer + description: The days to keep metric reported data. + pairwiseIdType: + type: string + description: The pairwise ID type. + pairwiseCalculationKey: + type: string + description: Key to calculate algorithmic pairwise IDs. + pairwiseCalculationSalt: + type: string + description: Salt to calculate algorithmic pairwise IDs. + shareSubjectIdBetweenClientsWithSameSectorId: + type: boolean + description: Share Subject ID between clients with same Sector ID. + webKeysStorage: + type: string + description: Web Key Storage Type. + enum: + - keystore + - pkcs11 + dnName: + type: string + description: DN of certificate issuer. + keyStoreFile: + type: string + description: The Key Store File (JKS). + example: /etc/certs/jans-auth-keys.jks + keyStoreSecret: + type: string + description: The password of the Key Store. + keySelectionStrategy: + type: string + description: Key Selection Strategy. + enum: + - OLDER + - NEWER + - FIRST + checkUserPresenceOnRefreshToken: + type: boolean + description: Check whether user exists and is active before creating RefreshToken. Set it to true if check is needed(Default value is false - don't check.) + example: false + default: false + oxElevenTestModeToken: + type: string + description: oxEleven Test Mode Token. + oxElevenGenerateKeyEndpoint: + type: string + description: URL for the oxEleven Generate Key Endpoint. + example: 'https://server.example.com/oxeleven/rest/oxeleven/generateKey' + oxElevenSignEndpoint: + type: string + description: URL for the oxEleven Sign Endpoint. + example: 'https://server.example.com/oxeleven/rest/oxeleven/sign' + oxElevenVerifySignatureEndpoint: + type: string + description: URL for the oxEleven Verify Signature Endpoint. + example: 'https://server.example.com/oxeleven/rest/oxeleven/verifySignature' + oxElevenDeleteKeyEndpoint: + type: string + description: URL for the oxEleven Delete Key Endpoint. + example: 'https://server.example.com/oxeleven/rest/oxeleven/deleteKey' + introspectionAccessTokenMustHaveUmaProtectionScope: + type: boolean + description: Reject introspection requests if access_token in Authorization header does not have uma_protection scope. + endSessionWithAccessToken: + type: boolean + description: Accept access token to call end_session endpoint. + cookieDomain: + type: string + description: Sets cookie domain for all cookies created by OP. + enabledOAuthAuditLogging: + type: boolean + description: enabled OAuth Audit Logging. + jmsBrokerURISet: + type: array + description: JMS Broker URI Set. + format: select + items: + type: string + jmsUserName: + type: string + description: JMS UserName. + jmsPassword: + type: string + description: JMS Password. + clientWhiteList: + type: array + description: White List for Client Redirection URIs. + items: + type: string + clientBlackList: + type: array + description: Black List for Client Redirection URIs. + items: + type: string + legacyIdTokenClaims: + type: boolean + description: Include Claims in ID Token. + customHeadersWithAuthorizationResponse: + type: boolean + description: Boolean value specifying whether to enable Custom Response Header parameter to return custom headers with the Authorization Response. + frontChannelLogoutSessionSupported: + type: boolean + description: Boolean value to specify support for front channel logout session. + loggingLevel: + type: string + description: Logging level for jans-auth logger. + enum: + - TRACE + - DEBUG + - INFO + - WARN + - ERROR + - FATAL + - OFF + loggingLayout: + type: string + description: Logging layout used for Jans Authorization Server loggers. + - text + - json + updateUserLastLogonTime: + type: boolean + description: Boolean value to specify if application should update oxLastLogonTime attribute on user authentication. + updateClientAccessTime: + type: boolean + description: Boolean value to specify if application should update oxLastAccessTime/oxLastLogonTime attributes on client authentication. + logClientIdOnClientAuthentication: + type: boolean + description: Boolean value to specify if application should log the Client ID on client authentication. + logClientNameOnClientAuthentication: + type: boolean + description: Boolean value to specify if application should log the Client Name on client authentication. + disableJdkLogger: + type: boolean + description: Boolean value specifying whether to enable JDK Loggers. + authorizationRequestCustomAllowedParameters: + type: array + description: Authorization Request Custom Allowed Parameters. + items: + type: string + legacyDynamicRegistrationScopeParam: + type: boolean + description: Legacy Dynamic Registration Scopes JSON Array Param. + openidScopeBackwardCompatibility: + type: boolean + description: Set to false to only allow token endpoint request for openid scope with grant type equals to authorization_code, restrict access to userinfo to scope openid and only return id_token if scope contains openid. + disableU2fEndpoint: + type: boolean + description: Enable/Disable U2F endpoints. + useLocalCache: + type: boolean + description: Boolean value specifying whether to enable local in-memory cache. + fapiCompatibility: + type: boolean + description: Boolean value specifying whether turn on FAPI compatibility mode. If true AS behaves in more strict mode. + forceIdTokenHintPrecense: + type: boolean + description: Boolean value specifying whether force id_token_hint parameter presence. + forceOfflineAccessScopeToEnableRefreshToken: + type: boolean + description: Boolean value specifying whether force offline_access scope to enable refresh_token grant type. + errorReasonEnabled: + type: boolean + description: Boolean value specifying whether to return detailed reason of the error from AS.. + removeRefreshTokensForClientOnLogout: + type: boolean + description: Boolean value specifying whether to remove refresh tokens on logout. + skipRefreshTokenDuringRefreshing: + type: boolean + description: Boolean value specifying whether to skip refreshing tokens on refreshing. + refreshTokenExtendLifetimeOnRotation: + type: boolean + description: Boolean value specifying whether to extend refresh tokens on rotation. + consentGatheringScriptBackwardCompatibility: + type: boolean + description: Boolean value specifying whether turn on Consent Gathering Script backward compatibility mode. If true AS will pick up script with higher level globally. If false AS will pick up script based on client configuration. + introspectionScriptBackwardCompatibility: + type: boolean + description: Boolean value specifying whether switch off client\'s introspection scripts (true value) and run all scripts that exists on server. + introspectionResponseScopesBackwardCompatibility: + type: boolean + description: Boolean value specifying introspection response backward compatibility mode. + softwareStatementValidationType: + type: string + description: Validation type used for software statement. + enum: + - none + - jwks + - jwks_uri + - script + softwareStatementValidationClaimName: + type: string + description: Validation claim name for software statement. + authenticationProtectionConfiguration: + type: object + description: Authentication Brute Force Protection Configuration. + $ref: '#/components/schemas/AuthenticationProtectionConfiguration' + errorHandlingMethod: + type: string + description: A list of possible error handling methods. + enum: + - internal + - remote + keepAuthenticatorAttributesOnAcrChange: + type: boolean + description: Boolean value specifying whether to keep authenticator attributes on ACR change. + deviceAuthzRequestExpiresIn: + type: integer + description: Expiration time given for device authorization requests. + deviceAuthzTokenPollInterval: + type: integer + description: Default interval returned to the client to process device token requests. + deviceAuthzResponseTypeToProcessAuthz: + type: string + description: Response type used to process device authz requests. + backchannelClientId: + type: string + description: Backchannel Client Id. + backchannelRedirectUri: + type: string + description: Backchannel Redirect Uri. + example: 'https://server.example.com/oxeleven/rest/backchannel/backchannelRedirectUri' + backchannelAuthenticationEndpoint: + type: string + description: Backchannel Authentication Endpoint. + example: 'https://server.example.com/oxeleven/rest/backchannel/backchannelAuthenticationEndpoint()' + backchannelDeviceRegistrationEndpoint: + type: string + description: Backchannel Device Registration Endpoint. + example: 'https://server.example.com/oxeleven/rest/backchannel/backchannelDeviceRegistrationEndpoint' + backchannelTokenDeliveryModesSupported: + type: array + description: Backchannel Token Delivery Modes Supported. + items: + type: string + enum: + - poll + - ping + - push + backchannelAuthenticationRequestSigningAlgValuesSupported: + type: array + description: Backchannel Authentication Request Signing Alg Values Supported. + items: + type: string + enum: + - RS512 + - ES256 + - ES384 + - ES512 + - PS256 + - PS384 + - PS512 + - RS384 + - RS256 + backchannelUserCodeParameterSupported: + type: boolean + description: Backchannel User Code Parameter Supported + backchannelBindingMessagePattern: + type: string + description: Backchannel Binding Message Pattern. + backchannelAuthenticationResponseExpiresIn: + type: integer + description: Backchannel Authentication Response Expires In. + backchannelAuthenticationResponseInterval: + type: integer + description: Backchannel Authentication Response Interval. + backchannelLoginHintClaims: + type: array + description: Backchannel Login Hint Claims. + items: + type: string + cibaEndUserNotificationConfig: + type: object + description: CIBA End User Notification Config. + $ref: '#/components/schemas/CIBAEndUserNotificationConfig' + backchannelRequestsProcessorJobIntervalSec: + type: integer + description: Specifies the allowable elapsed time in seconds backchannel request processor executes. + backchannelRequestsProcessorJobChunkSize: + type: integer + description: Each backchannel request processor iteration fetches chunk of data to be processed. + cibaGrantLifeExtraTimeSec: + type: integer + description: Specifies the CIBA Grant life extra time in seconds. + cibaMaxExpirationTimeAllowedSec: + type: integer + description: Specifies the CIBA token expiration time in seconds. + discoveryCacheLifetimeInMinutes: + type: integer + description: Lifetime of discovery cache. + httpLoggingEnabled: + type: boolean + description: Enable/Disable request/response logging filter. + httpLoggingExcludePaths: + type: array + description: List of base URI for which request/response logging filter should not record activity. + items: + type: string + example: '\"/auth/img\", \"/auth/stylesheet\"' + externalLoggerConfiguration: + type: string + description: Path to external log4j2 logging configuration. + example: /identity/logviewer/configure + dcrSignatureValidationEnabled: + type: boolean + description: Boolean value enables DCR signature validation. Default is false. + dcrSignatureValidationSharedSecret: + type: string + description: Specifies shared secret for Dynamic Client Registration. + dcrSignatureValidationSoftwareStatementJwksURIClaim: + type: string + description: Specifies claim name inside software statement. Value of claim should point to JWKS URI. + dcrSignatureValidationSoftwareStatementJwksClaim: + type: string + description: Specifies claim name inside software statement. Value of claim should point to inlined JWKS. + dcrSignatureValidationJwks: + type: string + description: Specifies JWKS for all DCR's validations. + dcrSignatureValidationJwksUri: + type: string + description: Specifies JWKS URI for all DCR's validations. + dcrAuthorizationWithClientCredentials: + type: boolean + description: Boolean value indicating if DCR authorization to be performed using client credentials. + statTimerIntervalInSeconds: + type: integer + description: Statistical data capture time interval. + statWebServiceIntervalLimitInSeconds: + type: integer + description: Statistical data capture time interval limit. + keyAlgsAllowedForGeneration: + type: array + description: List of algorithm allowed to be used for key generation. + items: + type: string + example: '\"RS256\", \"RS512\", \"ES384\", \"PS256\"' + discoveryAllowedKeys: + type: array + description: List of configuration response claim allowed to be displayed in discovery endpoint. + items: + type: string + example: 'authorization_endpoint, token_endpoint, jwks_uri, scopes_supported, response_types_supported, response_modes_supported, etc..' + allowIdTokenWithoutImplicitGrantTypes: + type: boolean + description: Specifies if a token without implicit grant types is allowed. + keySignWithSameKeyButDiffAlg: + type: boolean + description: Specifies if signing to be done with same key but apply different algorithms. + enabledComponents: + type: array + description: List of auth components enabled + items: + type: string + example: 'HEALTH_CHECK, USERINFO, CLIENTINFO, ID_GENERATION, REGISTRATION, INTROSPECTION, etc..' + staticKid: + type: string + description: Specifies static Kid + redirectUrisRegexEnabled: + type: boolean + description: Enable/Disable redirect uris validation using regular expression. + useHighestLevelScriptIfAcrScriptNotFound: + type: boolean + description: Enable/Disable usage of highest level script in case ACR script does not exist. + agamaConfiguration: + type: object + desciption: Engine Config which offers an alternative way to build authentication flows in Janssen server + $ref: '#/components/schemas/EngineConfig' + + GluuAttribute: + title: GluuAttribute + description: Attribute. + type: object + required: + - name + - description + - displayName + - dataType + - status + - viewType + - editType + properties: + dn: + type: string + inum: + description: XRI i-number. Identifier to uniquely identify the attribute. + type: string + name: + type: string + description: Name of the attribute. + example: 'name, displayName, birthdate, email' + displayName: + type: string + description: + type: string + description: User friendly descriptive detail of attribute. + dataType: + type: string + description: Data Type of attribute. + enum: + - STRING + - NUMERIC + - BOOLEAN + - BINARY + - CERTIFICATE + - DATE + - JSON + status: + type: string + description: Attrubute status + enum: + - ACTIVE + - INACTIVE + - EXPIRED + - REGISTER + lifetime: + type: string + sourceAttribute: + type: string + salt: + type: string + nameIdType: + type: string + origin: + type: string + editType: + type: array + description: GluuUserRole + items: + type: string + enum: + - ADMIN + - OWNER + - MANAGER + - USER + - WHITEPAGES + viewType: + type: array + description: GluuUserRole + items: + type: string + enum: + - ADMIN + - OWNER + - MANAGER + - USER + - WHITEPAGES + usageType: + type: array + description: GluuAttributeUsageType + items: + type: string + enum: + - openid + claimName: + type: string + seeAlso: + type: string + saml1Uri: + type: string + saml2Uri: + type: string + urn: + type: string + scimCustomAttr: + type: boolean + description: Boolean value indicating if the attribute is a SCIM custom attribute + oxMultiValuedAttribute: + type: boolean + description: Boolean value indicating if the attribute can hold multiple value. + attributeValidation: + type: object + description: Details of validations to be applied on the attribute + properties: + regexp: + type: string + description: Reguar expression to be used to validate the dataType. + minLength: + type: integer + maxLength: + type: integer + tooltip: + type: string + jansHideOnDiscovery: + type: boolean + description: Boolean value indicating if the attribute should be shown on that discovery page. + PatchRequest: + description: A JSONPatch document as defined by RFC 6902 + required: + - "op" + - "path" + properties: + op: + type: string + description: The operation to be performed + enum: + - "add" + - "remove" + - "replace" + - "move" + - "copy" + - "test" + path: + type: string + description: A JSON-Pointer + example: '/client/customattribute/[0]' + value: + type: object + description: The value to be used within the operations. + Scope: + title: Scope + description: Auth Scope. + type: object + required: + - id + - scopeType + properties: + dn: + type: string + inum: + description: Unique id identifying the . + type: string + displayName: + description: A human-readable name of the scope. + type: string + id: + description: The base64url encoded id. + type: string + iconUrl: + description: A URL for a graphic icon representing the scope. The referenced icon MAY be used by the authorization server in any user interface it presents to the resource owner. + type: string + description: + description: A human-readable string describing the scope. + type: string + scopeType: + description: The scopes type associated with Access Tokens determine what resources will. + type: string + enum: + - openid + - dynamic + - uma + - spontaneous + - oauth + claims: + description: Claim attributes associated with the scope. + type: array + items: + type: string + defaultScope: + description: Boolean value to specify default scope. + type: boolean + groupClaims: + description: Specifies if the scope is group claims. + type: boolean + dynamicScopeScripts: + description: Dynamic Scope Scripts associated with the scope. + type: array + items: + type: string + umaAuthorizationPolicies: + description: Policies associated with scopes. + type: array + items: + type: string + attributes: + type: object + description: ScopeAttributes + properties: + spontaneousClientId: + type: string + spontaneousClientScopes: + type: array + items: + type: string + showInConfigurationEndpoint: + type: boolean + umaType: + description: Specifies if the scope is of type UMA. + type: boolean + default: false + deletable: + description: Specifies if the scope can be deleted. + type: boolean + default: false + expirationDate: + description: Expiry date of the Scope. + type: string + format: date + CustomAttribute: + title: CustomAttribute + description: Attribute. + type: object + required: + - name + - multiValued + - values + properties: + name: + type: string + description: Name of the attribute. + example: 'name, displayName, birthdate, email' + multiValued: + type: boolean + description: Indicates if the attribute can hold multiple values. + values: + type: array + items: + type: string + ClientAttributes: + title: ClientAttributes + description: Attribute. + type: object + properties: + tlsClientAuthSubjectDn: + description: String representation of the expected subject distinguished name of the certificate, which the OAuth client will use in mutual TLS authentication. + type: string + runIntrospectionScriptBeforeAccessTokenAsJwtCreationAndIncludeClaims: + description: boolean property which indicates whether to run introspection script and then include claims from result into access_token as JWT. + type: boolean + keepClientAuthorizationAfterExpiration: + description: boolean property which indicates whether to keep client authorization after expiration. + type: boolean + allowSpontaneousScopes: + description: boolean, whether to allow spontaneous scopes for client. + type: boolean + spontaneousScopes: + description: List of spontaneous scope regular expression. + type: array + items: + type: string + spontaneousScopeScriptDns: + description: List of spontaneous scope scripts. + type: array + items: + type: string + updateTokenScriptDns: + description: List of update token scripts. + type: array + items: + type: string + backchannelLogoutUri: + description: List of RP URL that will cause the RP to log itself out when sent a Logout Token by the OP. + type: array + items: + type: string + backchannelLogoutSessionRequired: + description: Boolean value specifying whether the RP requires that a sid (session ID) Claim be included in the Logout Token to identify the RP session with the OP when true. Default value is false. + type: boolean + additionalAudience: + description: List of additional client audience. + type: array + items: + type: string + postAuthnScripts: + description: List of post authentication scripts. + type: array + items: + type: string + consentGatheringScripts: + description: List of consent gathering scripts. + type: array + items: + type: string + introspectionScripts: + description: List of introspection scripts. + type: array + items: + type: string + rptClaimsScripts: + description: List of Requesting Party Token (RPT) claims scripts. + type: array + items: + type: string + ropcScripts: + description: List of Resource Owner Password Credentials (ROPC) scripts. + type: array + items: + type: string + parLifetime: + description: represents the lifetime of Pushed Authorisation Request (PAR). + type: integer + format: int64 + requirePar: + description: boolean value to indicate of Pushed Authorisation Request(PAR)is required. + type: boolean + authorizationSignedResponseAlg: + description: JWS alg algorithm JWA required for signing authorization responses. + type: string + authorizationEncryptedResponseAlg: + description: JWE alg algorithm JWA required for encrypting authorization responses. + type: string + authorizationEncryptedResponseEnc: + description: JWE enc algorithm JWA required for encrypting auhtorization responses. + type: string + publicSubjectIdentifierAttribute: + description: custom subject identifier attribute. + type: string + redirectUrisRegex: + description: If set, redirectUri must match to this regexp + type: string + authorizedAcrValues: + description: List of thentication Context Class Reference (ACR) that must exist. + type: array + items: + type: string + defaultPromptLogin: + description: sets prompt=login to the authorization request, which causes the authorization server to force the user to sign in again before it will show the authorization prompt. + type: boolean + + + Client: + title: Client object + description: Client. + type: object + required: + - redirectUris + properties: + dn: + type: string + inum: + description: XRI i-number. Client Identifier to uniquely identify the client. + type: string + displayName: + type: string + description: Name of the user suitable for display to end-users + clientSecret: + type: string + description: The client secret. The client MAY omit the parameter if the client secret is an empty string. + frontChannelLogoutUri: + type: string + frontChannelLogoutSessionRequired: + type: boolean + registrationAccessToken: + type: string + clientIdIssuedAt: + type: string + format: date-time + clientSecretExpiresAt: + type: string + format: date-time + redirectUris: + description: Redirection URI values used by the Client. One of these registered Redirection URI values must exactly match the redirect_uri parameter value used in each Authorization Request + type: array + items: + type: string + example: + - 'https://client.example.org/cb' + claimRedirectUris: + description: Array of The Claims Redirect URIs to which the client wishes the authorization server to direct the requesting party's user agent after completing its interaction. + type: array + items: + type: string + responseTypes: + description: 'A list of the OAuth 2.0 response_type values that the Client is declaring that it will restrict itself to using. If omitted, the default is that the Client will use only the code Response Type. Allowed values are code, token, id_token.' + type: array + items: + type: string + enum: + - code + - token + - id_token + grantTypes: + description: A list of the OAuth 2.0 Grant Types that the Client is declaring that it will restrict itself to using. + type: array + items: + type: string + enum: + - authorization_code + - implicit + - password + - client_credentials + - refresh_token + - urn:ietf:params:oauth:grant-type:uma-ticket + - urn:openid:params:grant-type:ciba + - urn:ietf:params:oauth:grant-type:device_code + applicationType: + description: 'Kind of the application. The default, if omitted, is web. The defined values are native or web. Web Clients using the OAuth Implicit Grant Type must only register URLs using the HTTPS scheme as redirect_uris, they must not use localhost as the hostname. Native Clients must only register redirect_uris using custom URI schemes or URLs using the http scheme with localhost as the hostname.' + type: string + enum: + - web + - native + contacts: + description: e-mail addresses of people responsible for this Client. + type: array + items: + type: string + idTokenTokenBindingCnf: + description: 'Specifies the JWT Confirmation Method member name (e.g. tbh) that the Relying Party expects when receiving Token Bound ID Tokens. The presence of this parameter indicates that the Relying Party supports Token Binding of ID Tokens. If omitted, the default is that the Relying Party does not support Token Binding of ID Tokens.' + type: string + logoUri: + description: URL that references a logo for the Client application. + type: string + clientUri: + description: URL of the home page of the Client. The value of this field must point to a valid Web page. + type: string + policyUri: + description: URL that the Relying Party Client provides to the End-User to read about the how the profile data will be used. + type: string + tosUri: + description: URL that the Relying Party Client provides to the End-User to read about the Relying Party's terms of service. + type: string + jwksUri: + description: 'URL for the Client''s JSON Web Key Set (JWK) document containing key(s) that are used for signing requests to the OP. The JWK Set may also contain the Client''s encryption keys(s) that are used by the OP to encrypt the responses to the Client. When both signing and encryption keys are made available, a use (Key Use) parameter value is required for all keys in the document to indicate each key''s intended usage.' + type: string + jwks: + description: 'List of JSON Web Key (JWK) - A JSON object that represents a cryptographic key. The members of the object represent properties of the key, including its value.' + type: string + example: '{ "keys" : [ { "e" : "AQAB", "n" : "gmlDX_mgMcHX.." ] }' + sectorIdentifierUri: + description: URL using the https scheme to be used in calculating Pseudonymous Identifiers by the OP. + type: string + subjectType: + description: Subject type requested for the Client ID. Valid types include pairwise and public. + type: string + enum: + - pairwise + - public + idTokenSignedResponseAlg: + description: JWS alg algorithm (JWA) required for signing the ID Token issued to this Client. + type: string + enum: + - HS256 + - HS384 + - HS512 + - RS256 + - RS384 + - RS512 + - ES256 + - ES384 + - ES512 + - PS256 + - PS384 + - PS512 + idTokenEncryptedResponseAlg: + description: JWE alg algorithm (JWA) required for encrypting the ID Token issued to this Client. + type: string + enum: + - RSA1_5 + - RSA-OAEP + - A128KW + - A256KW + idTokenEncryptedResponseEnc: + description: JWE enc algorithm (JWA) required for encrypting the ID Token issued to this Client. + type: string + enum: + - A128CBC+HS256 + - A256CBC+HS512 + - A128GCM + - A256GCM + userInfoSignedResponseAlg: + description: JWS alg algorithm (JWA) required for signing UserInfo Responses. + type: string + enum: + - HS256 + - HS384 + - HS512 + - RS256 + - RS384 + - RS512 + - ES256 + - ES384 + - ES512 + - PS256 + - PS384 + - PS512 + userInfoEncryptedResponseAlg: + description: JWE alg algorithm (JWA) required for encrypting UserInfo Responses. + type: string + enum: + - RSA1_5 + - RSA-OAEP + - A128KW + - A256KW + userInfoEncryptedResponseEnc: + description: JWE enc algorithm (JWA) required for encrypting UserInfo Responses. + type: string + enum: + - A128CBC+HS256 + - A256CBC+HS512 + - A128GCM + - A256GCM + requestObjectSigningAlg: + description: JWS alg algorithm (JWA) that must be used for signing Request Objects sent to the OP. + type: string + enum: + - HS256 + - HS384 + - HS512 + - RS256 + - RS384 + - RS512 + - ES256 + - ES384 + - ES512 + - PS256 + - PS384 + - PS512 + requestObjectEncryptionAlg: + description: JWE alg algorithm (JWA) the RP is declaring that it may use for encrypting Request Objects sent to the OP. + type: string + enum: + - RSA1_5 + - RSA-OAEP + - A128KW + - A256KW + requestObjectEncryptionEnc: + description: JWE enc algorithm (JWA) the RP is declaring that it may use for encrypting Request Objects sent to the OP. + type: string + enum: + - A128CBC+HS256 + - A256CBC+HS512 + - A128GCM + - A256GCM + tokenEndpointAuthMethod: + description: Requested Client Authentication method for the Token Endpoint. + type: string + enum: + - client_secret_basic + - client_secret_post + - client_secret_jwt + - private_key_jwt + - tls_client_auth + - none + tokenEndpointAuthSigningAlg: + description: JWS alg algorithm (JWA) that must be used for signing the JWT used to authenticate the Client at the Token Endpoint for the private_key_jwt and client_secret_jwt authentication methods. + type: string + enum: + - HS256 + - HS384 + - HS512 + - RS256 + - RS384 + - RS512 + - ES256 + - ES384 + - ES512 + - PS256 + - PS384 + - PS512 + defaultMaxAge: + description: Specifies the Default Maximum Authentication Age. + type: integer + format: int32 + example: 1000000 + requireAuthTime: + description: Boolean value specifying whether the auth_time Claim in the ID Token is required. It is required when the value is true. + type: boolean + defaultAcrValues: + description: Array of default requested Authentication Context Class Reference values that the Authorization Server must use for processing requests from the Client. + type: array + items: + type: string + initiateLoginUri: + description: Specifies the URI using the https scheme that the authorization server can call to initiate a login at the client. + type: string + postLogoutRedirectUris: + description: Provide the URLs supplied by the RP to request that the user be redirected to this location after a logout has been performed. + type: array + items: + type: string + example: + - 'https://client.example.org/logout/page1' + - 'https://client.example.org/logout/page2' + - 'https://client.example.org/logout/page3' + requestUris: + description: Provide a list of requests_uri values that are pre-registered by the Client for use at the Authorization Server. + type: array + items: + type: string + scopes: + description: Provide list of scopes granted to the client (scope dn or scope id). + type: array + items: + type: string + example: + - read write dolphin + claims: + description: Provide list of claims granted to the client. + type: array + items: + type: string + description: String containing a space-separated list of claims that can be requested individually. + trustedClient: + description: Attribute which corresponds to the "Pre-Authorization" property. Default value is false. + type: boolean + default: false + lastAccessTime: + description: 'Integer timestamp, measured in the number of seconds since January 1 1970 UTC, indicating last access time.' + type: string + format: date-time + lastLogonTime: + description: 'Integer timestamp, measured in the number of seconds since January 1 1970 UTC, indicating last login time.' + type: string + format: date-time + persistClientAuthorizations: + description: Specifies if the client authorization details are to be persisted. Default value is true. + type: boolean + includeClaimsInIdToken: + description: 'If true then claims are included in token id, default value is false.' + type: boolean + default: false + refreshTokenLifetime: + description: Specifies the Client-specific refresh token expiration. + type: integer + format: int32 + example: 100000000 + accessTokenLifetime: + description: Specifies the Client-specific access token expiration. + type: integer + format: int32 + example: 100000000 + customAttributes: + type: array + items: + $ref: '#/components/schemas/CustomAttribute' + customObjectClasses: + type: array + items: + type: string + rptAsJwt: + description: Specifies whether RPT should be return as signed JWT. + type: boolean + accessTokenAsJwt: + description: Specifies whether access token as signed JWT. + type: boolean + accessTokenSigningAlg: + description: 'Specifies signing algorithm that has to be used during JWT signing. If it''s not specified, then the default OP signing algorithm will be used.' + type: string + enum: + - HS256 + - HS384 + - HS512 + - RS256 + - RS384 + - RS512 + - ES256 + - ES384 + - ES512 + - PS256 + - PS384 + - PS512 + disabled: + description: Specifies whether client is disabled. + type: boolean + default: false + authorizedOrigins: + description: Specifies authorized JavaScript origins. + type: array + items: + type: string + softwareId: + description: Specifies a unique identifier string (UUID) assigned by the client developer or software publisher used by registration endpoints to identify the client software to be dynamically registered. + type: string + example: 4NRB1-0XZABZI9E6-5SM3R + softwareVersion: + description: Specifies a version identifier string for the client software identified by 'software_id'. The value of the 'software_version' should change on any update to the client software identified by the same 'software_id'. + type: string + example: '2.1' + softwareStatement: + description: Specifies a software statement containing client metadata values about the client software as claims. This is a string value containing the entire signed JWT. + type: string + attributes: + type: object + $ref: '#/components/schemas/ClientAttributes' + backchannelTokenDeliveryMode: + description: specifies how backchannel token will be delivered. + type: string + enum: + - poll + - ping + - push + backchannelClientNotificationEndpoint: + description: 'Client Initiated Backchannel Authentication (CIBA) enables a Client to initiate the authentication of an end-user by means of out-of-band mechanisms. Upon receipt of the notification, the Client makes a request to the token endpoint to obtain the tokens.' + type: string + backchannelAuthenticationRequestSigningAlg: + description: 'The JWS algorithm alg value that the Client will use for signing authentication request, as described in Section 7.1.1. of OAuth 2.0 [RFC6749]. When omitted, the Client will not send signed authentication requests.' + type: string + enum: + - RS256 + - RS384 + - RS512 + - ES256 + - ES384 + - ES512 + - PS256 + - PS384 + - PS512 + backchannelUserCodeParameter: + description: 'Boolean value specifying whether the Client supports the user_code parameter. If omitted, the default value is false.' + type: boolean + expirationDate: + description: Integer timestamp, measured in the number of seconds since January 1 1970 UTC, indicating when this permission will expire. + type: string + format: date-time + deletable: + description: Specifies whether client is deletable. + type: boolean + default: false + jansId: + description: Attribute Scope Id. + type: string + description: + description: Description of the client. + type: string + + UmaResource: + title: UMAResource object + description: UMAResource + type: object + required: + - id + - type + - iconUri + properties: + dn: + type: string + inum: + description: XRI i-number. Client Identifier to uniquely identify the UMAResource. + type: string + id: + description: Resource id. + type: string + name: + description: A human-readable name of the scope. + type: string + iconUri: + description: A URL for a graphic icon representing the resource. + type: string + scopes: + description: Applicable resource scopes. + type: array + items: + type: string + scopeExpression: + description: Resource scope expression. + type: string + clients: + description: List of client assosiated with the resource. + type: array + items: + type: string + resources: + description: List of assosiated resource. + type: array + items: + type: string + rev: + description: Resource revision. + type: string + creator: + description: Resource creator or owner. + type: string + description: + description: Resource description. + type: string + type: + description: Resource type. + type: string + creationDate: + description: 'Integer timestamp, measured in the number of seconds since January 1 1970 UTC, indicating when this resource will created.' + type: string + format: date-time + expirationDate: + description: 'Integer timestamp, measured in the number of seconds since January 1 1970 UTC, indicating when this resource will expire.' + type: string + format: date-time + deletable: + description: Specifies whether client is deletable. + type: boolean + default: false + SectorIdentifier: + type: object + description: Sector Identifier Details. + required: + - id + properties: + id: + description: XRI i-number. Sector Identifier to uniquely identify the sector. + type: string + description: + description: A human-readable string describing the sector. + type: string + redirectUris: + description: Redirection URI values used by the Client. One of these registered Redirection URI values must exactly match the redirect_uri parameter value used in each Authorization Request + type: array + items: + type: string + example: + - 'https://client.example.org/cb' + clientIds: + description: List of OAuth 2.0 Client Identifier valid at the Authorization Server. + type: array + items: + type: string + example: 1402.0ab17362-36cc-4ac8-9c73-20239de64364 API Requesting Party Client. + CacheConfiguration: + type: object + description: Cache Configuration Details. + properties: + cacheProviderType: + description: The cache Provider Type. + type: string + enum: + - IN_MEMORY + - MEMCACHED + - REDIS + - NATIVE_PERSISTENCE + memcachedConfiguration: + type: object + $ref: '#/components/schemas/MemcachedConfiguration' + redisConfiguration: + type: object + $ref: '#/components/schemas/RedisConfiguration' + inMemoryConfiguration: + type: object + $ref: '#/components/schemas/InMemoryConfiguration' + nativePersistenceConfiguration: + type: object + $ref: '#/components/schemas/NativePersistenceConfiguration' + MemcachedConfiguration: + description: Memcached cache configuration. + type: object + properties: + servers: + type: string + description: Server details separated by spaces. + format: url + minLength: 1 + maxOperationQueueLength: + type: integer + description: Maximum operation Queue Length. + bufferSize: + type: integer + description: Buffer Size. + defaultPutExpiration: + type: integer + description: Expiration timeout value. + connectionFactoryType: + type: string + description: The MemcachedConnectionFactoryType Type. + enum: + - DEFAULT + - BINARY + RedisConfiguration: + type: object + description: Cache Configuration + properties: + redisProviderType: + description: Type of connection. + type: string + enum: + - STANDALONE + - CLUSTER + - SHARDED + - SENTINEL + servers: + description: 'server details separated by comma e.g. ''server1:8080server2:8081''.' + type: string + title: servers + format: url + password: + description: Redis password. + type: string + defaultPutExpiration: + description: defaultPutExpiration timeout value. + type: integer + sentinelMasterGroupName: + description: Sentinel Master Group Name (required if SENTINEL type of connection is selected). + type: string + useSSL: + description: Enable SSL communication between Gluu Server and Redis cache. + type: boolean + sslTrustStoreFilePath: + description: Directory Path to Trust Store. + type: string + format: url + maxIdleConnections: + description: The cap on the number of \idle\ instances in the pool. If max idle is set too low on heavily loaded systems it is possible you will see objects being destroyed and almost immediately new objects being created. This is a result of the active threads momentarily returning objects faster than they are requesting them causing the number of idle objects to rise above max idle. The best value for max idle for heavily loaded system will vary but the default is a good starting point. + type: integer + maxTotalConnections: + description: The number of maximum connection instances in the pool. + type: integer + connectionTimeout: + description: Connection time out. + type: integer + soTimeout: + description: With this option set to a non-zero timeout a read() call on the InputStream associated with this Socket will block for only this amount of time. If the timeout expires a java.net.SocketTimeoutException is raised though the Socket is still valid. The option must be enabled prior to entering the blocking operation to have effect. The timeout must be > 0. A timeout of zero is interpreted as an infinite timeout. + type: integer + maxRetryAttempts: + description: Maximum retry attempts in case of failure. + type: integer + InMemoryConfiguration: + type: object + description: Cache configuration. + properties: + defaultPutExpiration: + description: defaultPutExpiration timeout value. + type: integer + NativePersistenceConfiguration: + type: object + description: Cache configuration. + properties: + defaultPutExpiration: + description: defaultPutExpiration timeout value. + type: integer + defaultCleanupBatchSize: + description: defaultCleanupBatchSize page size. + type: integer + deleteExpiredOnGetRequest: + type: boolean + SmtpConfiguration: + type: object + description: SMTP configuration. + properties: + host: + description: Hostname of the SMTP server. + type: string + format: url + port: + description: Port number of the SMTP server. + type: integer + format: int32 + multipleOf: 1 + requires_ssl: + description: Boolean value with default value false. If true, SSL will be enabled. + type: boolean + trust_host: + type: boolean + description: Boolean value with default value false. + from_name: + description: Name of the sender. + type: string + from_email_address: + description: Email Address of the Sender. + type: string + requires_authentication: + description: Boolean value with default value false. It true it will enable sender authentication. + type: boolean + user_name: + description: Username of the SMTP. + type: string + password: + description: Password for the SMTP. + type: string + LoggingConfiguration: + type: object + description: Log configuration. + properties: + loggingLevel: + type: string + description: Logging level for Jans Authorization Server logger. + enum: + - TRACE + - DEBUG + - INFO + - WARN + - ERROR + - FATAL + - 'OFF' + loggingLayout: + type: string + description: Logging layout used for Jans Authorization Server loggers. + enum: + - text + - json + httpLoggingEnabled: + description: To enable http request/response logging. + type: boolean + disableJdkLogger: + description: To enable/disable Jdk logging. + type: boolean + enabledOAuthAuditLogging: + description: To enable/disable OAuth audit logging. + type: boolean + externalLoggerConfiguration: + description: Path to external log4j2 configuration file. + type: string + httpLoggingExcludePaths: + description: List of paths to exclude from logger. + type: array + items: + type: string + example: + - /auth/img + - /auth/stylesheet + WebKeysConfiguration: + type: object + description: 'JSON Web Key Set (JWKS) - A JSON object that represents a set of JWKs. The JSON object MUST have a keys member, which is an array of JWKs.' + required: + - keys + properties: + keys: + type: array + description: 'List of JSON Web Key (JWK) - A JSON object that represents a cryptographic key. The members of the object represent properties of the key, including its value.' + items: + $ref: '#/components/schemas/JsonWebKey' + + AuthenticationMethod: + type: object + description: Authentication Method Configuration + properties: + defaultAcr: + type: string + description: This field controls the default authentication mechanism that is presented to users from all applications that leverage Janssen Server for authentication. + + JansFido2DynConfiguration: + type: object + description: Jans Fido2 dynamic configuration properties. + properties: + issuer: + type: string + description: URL using the https scheme for Issuer identifier. + example: 'https://server.example.com/' + baseEndpoint: + type: string + description: The base URL for Fido2 endpoints. + example: 'https://server.example.com/fido2/restv1' + cleanServiceInterval: + type: integer + description: Time interval for the Clean Service in seconds. + cleanServiceBatchChunkSize: + type: integer + description: Each clean up iteration fetches chunk of expired data per base dn and removes it from storage. + useLocalCache: + description: Boolean value to indicate if Local Cache is to be used. + type: boolean + disableJdkLogger: + type: boolean + description: Boolean value specifying whether to enable JDK Loggers. + loggingLevel: + type: string + description: Logging level for Fido2 logger. + loggingLayout: + type: string + description: Logging layout used for Fido2. + externalLoggerConfiguration: + type: string + description: Path to external Fido2 logging configuration. + metricReporterInterval: + type: integer + description: The interval for metric reporter in seconds. + metricReporterKeepDataDays: + type: integer + description: The days to keep report data. + personCustomObjectClassList: + type: array + description: Custom object class list for dynamic person enrolment. + items: + type: string + fido2Configuration: + description: Fido2Configuration. + $ref: '#/components/schemas/Fido2Configuration' + + Fido2RegistrationEntry: + type: object + description: Fido2 registration entry + properties: + publicKeyId: + description: Public key id + type: string + displayName: + description: Dislay name + type: string + counter: + description: counter + type: integer + deviceNotificationConf: + description: Device notification configuration + type: string + challangeHash: + description: Challange hash + type: string + registrationData: + description: Fido2 registration data. + $ref: '#/components/schemas/Fido2RegistrationData' + registrationStatus: + description: registration status + type: string + enum: + - pending + - registered + - compromised + + Fido2RegistrationData: + type: object + description: Fido2 registration data. + properties: + username: + description: Username + type: string + domain: + description: Domain + type: string + userId: + description: user id + type: string + challenge: + description: challenge + type: string + attenstationRequest: + description: Attenstation request + type: string + attenstationResponse: + description: Attenstation response + type: string + uncompressedECPoint: + description: uncompressed EC point + type: string + publicKeyId: + description: public key id + type: string + type: + description: type + type: string + counter: + description: counter + type: integer + attestationType: + description: attestation type + type: string + signatureAlgorithm: + description: signature algorithm + type: integer + applicationId: + description: application id + type: string + status: + description: status + type: string + enum: + - pending + - registered + - compromised + + Fido2Configuration: + type: object + description: Fido2 configuration properties. + properties: + authenticatorCertsFolder: + description: Authenticators certificates fodler. + type: string + mdsCertsFolder: + description: MDS TOC root certificates folder. + type: string + mdsTocsFolder: + description: MDS TOC files folder. + type: string + serverMetadataFolder: + description: Authenticators metadata in json format. + type: string + requestedParties: + description: Authenticators metadata in json format. + type: array + items: + $ref: '#/components/schemas/RequestedParties' + userAutoEnrollment: + description: Allow to enroll users on enrollment/authentication requests. + type: boolean + unfinishedRequestExpiration: + description: Expiration time in seconds for pending enrollment/authentication requests + type: integer + authenticationHistoryExpiration: + description: Expiration time in seconds for approved authentication requests. + type: integer + requestedCredentialTypes: + description: List of Requested Credential Types. + type: array + items: + type: string + RequestedParties: + type: object + description: Credential Type. + properties: + name: + description: Name of the Requested Party. + type: string + format: url + domains: + description: Requested Party domains. + type: array + items: + type: string + SimpleCustomProperty: + type: object + description: Simple Property. + properties: + value1: + type: string + value2: + type: string + description: + type: string + SimpleExtendedCustomProperty: + type: object + description: Simple Extended Property. + properties: + value1: + type: string + value2: + type: string + description: + type: string + hide: + type: boolean + ScriptError: + type: object + description: Possible errors assosiated with the script. + properties: + raisedAt: + type: string + format: date-time + stackTrace: + type: string + AuthenticationFilters: + type: object + description: Represents the authentication filter. + properties: + filter: + type: string + description: Filter to be used. + example: 'myCustomAttr1={0}' + bind: + type: boolean + description: Filter bind. + bind-password-attribute: + type: string + description: Filter bind password attribute. + base-dn: + type: string + description: Bind filter base distinguished name. + example: 'ou=clients,o=gluu' + CorsConfigurationFilter: + type: object + description: CORS Configuration Filter. + properties: + filterName: + type: string + description: Filter name. + corsEnabled: + type: boolean + description: Boolean value indicating if the filter is enabled. + corsAllowedOrigins: + type: string + description: A list of origins that are allowed to access the resource. A * can be specified to enable access to resource from any origin. Otherwise, a whitelist of comma separated origins can be provided. + corsAllowedMethods: + type: string + description: A comma separated list of HTTP methods that can be used to access the resource, using cross-origin requests. These are the methods which will also be included as part of Access-Control-Allow-Methods header in pre-flight response. + corsAllowedHeaders: + type: string + description: The names of the supported author request headers. + corsExposedHeaders: + type: string + description: A comma separated list of request headers that can be used when making an actual request. These headers will also be returned as part of Access-Control-Allow-Headers header in a pre-flight response. + corsSupportCredentials: + type: boolean + description: A flag that indicates whether the resource supports user credentials. This flag is exposed as part of Access-Control-Allow-Credentials header in a pre-flight response. It helps browser determine whether or not an actual request can be made using credentials. + corsLoggingEnabled: + type: boolean + description: Value to enable logging, Setting the value to False will disable logging. + corsPreflightMaxAge: + type: integer + description: The duration in seconds the browser is allowed to cache the result of the pre-flight request. + corsRequestDecorate: + type: boolean + description: A flag to control if CORS specific attributes should be added to the HttpServletRequest object. + + AuthenticationProtectionConfiguration: + type: object + description: Authentication Brute Force Protection Configuration. + properties: + attemptExpiration: + type: integer + description: How long store in cache information about particular login attempt. It's needed to count login attempts withing specified period of time. + maximumAllowedAttemptsWithoutDelay: + type: integer + description: How many attempts application allow without delay. + delayTime: + type: integer + description: Delay time in seconds after reaching maximumAllowedAttemptsWithoutDelay limit. + bruteForceProtectionEnabled: + type: boolean + description: Enable or disable service, This functionality can be enabled dynamically. + + CIBAEndUserNotificationConfig: + type: object + description: CIBA End User Notification Config. + properties: + apiKey: + type: string + description: API Key + authDomain: + type: string + description: Auth Domain + databaseURL: + type: string + description: Database URL + projectId: + type: string + description: Project ID + storageBucket: + type: string + description: Storage Bucket + messagingSenderId: + type: string + description: Messaging Sender ID + appId: + type: string + description: App ID + notificationUrl: + type: string + description: Notification URL + notificationKey: + type: string + description: Notification Key + publicVapidKey: + type: string + description: Public Vapid Key + + + StatResponseItem: + type: object + description: Server statistics data + properties: + month: + type: integer + monthly_active_users: + type: integer + format: int64 + description: Number of active users + default: 0 + token_count_per_granttype: + type: object + additionalProperties: + $ref: '#/components/schemas/TokenMapObject' + + TokenMapObject: + type: object + description: A hashmap with statistical item as a key and the value as statistical value. + additionalProperties: + type: integer + format: int64 + + HealthStatus: + type: object + description: Server health data + properties: + status: + type: string + description: Health parameter name + error: + type: string + description: error message in case of error + checks: + type: array + items: + $ref: '#/components/schemas/HealthStatusItem' + description: health check status details. + example: '"checks": [{"name": "jans-config-api liveness","status": "UP"},{"name": "jans-config-api readiness","status": "UP"}],"status": "UP"}' + + + HealthStatusItem: + type: object + description: Server health data + properties: + name: + type: string + description: Health parameter name + status: + type: string + description: Health parameter status + + SearchRequest: + type: object + description: Search Parameters. + properties: + schemas: + type: array + items: + type: string + description: schema details + attributes: + type: array + items: + type: string + description: attribute details + excludedAttributes: + type: array + items: + type: string + description: attribute to be excluded details + filter: + type: string + description: search filter + sortBy: + type: string + description: attribute to be used for sorting + sortOrder: + type: string + description: sorting order + startIndex: + type: integer + format: int32 + description: result start index + count: + type: integer + format: int32 + description: total count of records + + UserListResponse: + description: Results for users search. See section 3.4.2.4 of RFC 7644 + allOf: + - $ref: '#/components/schemas/BasicListResponse' + - type: object + - type: object + properties: + Resources: + type: array + items: + $ref: '#/components/schemas/UserResource' + BasicListResponse: + type: object + properties: + schemas: + type: array + items: + type: string + example: urn:ietf:params:scim:api:messages:2.0:ListResponse + totalResults: + type: integer + description: Total number of results returned by the search. The value may be larger than the number of resources returned due to pagination + startIndex: + type: integer + description: The 1-based index of the first result in the current set of search results + itemsPerPage: + type: integer + description: The number of resources returned in a results page + + UserResource: + description: Represents a user resource. See section 4.1 of RFC 7643 + allOf: + - $ref: '#/components/schemas/BaseResource' + - type: object + - type: object + properties: + externalId: + type: string + description: Identifier of the resource useful from the perspective of the provisioning client. See section 3.1 of RFC 7643 + userName: + type: string + description: Identifier for the user, typically used by the user to directly authenticate (id and externalId are opaque identifiers generally not known by users) + name: + $ref: '#/components/schemas/Name' + displayName: + type: string + description: Name of the user suitable for display to end-users + nickName: + type: string + description: Casual way to address the user in real life + profileUrl: + type: string + description: URI pointing to a location representing the User's online profile + title: + type: string + example: Vice President + userType: + type: string + description: Used to identify the relationship between the organization and the user + example: Contractor + preferredLanguage: + type: string + description: Preferred language as used in the Accept-Language HTTP header + example: en + locale: + type: string + description: Used for purposes of localizing items such as currency and dates + example: en-US + timezone: + type: string + example: America/Los_Angeles + active: + type: boolean + password: + type: string + emails: + type: array + items: + $ref: '#/components/schemas/Email' + phoneNumbers: + type: array + items: + $ref: '#/components/schemas/PhoneNumber' + ims: + type: array + items: + $ref: '#/components/schemas/InstantMessagingAddress' + photos: + type: array + items: + $ref: '#/components/schemas/Photo' + addresses: + type: array + items: + $ref: '#/components/schemas/Address' + groups: + type: array + items: + $ref: '#/components/schemas/Group' + entitlements: + type: array + items: + $ref: '#/components/schemas/Entitlement' + roles: + type: array + items: + $ref: '#/components/schemas/Role' + x509Certificates: + type: array + items: + $ref: '#/components/schemas/X509Certificate' + urn:ietf:params:scim:schemas:extension:gluu:2.0:User: + type: object + properties: {} + description: Extended attributes + + Name: + type: object + properties: + familyName: + type: string + givenName: + type: string + middleName: + type: string + honorificPrefix: + type: string + description: A "title" like "Ms.", "Mrs." + honorificSuffix: + type: string + description: Name suffix, like "Junior", "The great", "III" + formatted: + type: string + description: Full name, including all middle names, titles, and suffixes as appropriate + description: See section 4.1.1 of RFC 7643 + Email: + type: object + properties: + value: + description: E-mail addresses for the user. + type: string + example: gossow@nsfw.com + display: + description: A human readable name, primarily used for display purposes. + type: string + type: + description: A label indicating the attribute's function; e.g., 'work' or 'home'. + type: string + example: work + primary: + type: boolean + description: Denotes if this is the preferred e-mail among others, if any + description: See section 4.1.2 of RFC 7643 + PhoneNumber: + type: object + properties: + value: + description: Phone number of the User + type: string + example: +1-555-555-8377 + display: + description: A human readable name, primarily used for display purposes. + type: string + type: + description: A label indicating the attribute's function; e.g., 'work' or 'home' or 'mobile' etc. + type: string + example: fax + primary: + description: A Boolean value indicating the 'primary' or preferred attribute value for this attribute. + type: boolean + InstantMessagingAddress: + type: object + properties: + value: + description: Instant messaging address for the User. + type: string + display: + description: A human readable name, primarily used for display purposes. + type: string + type: + description: A label indicating the attribute's function; e.g., 'aim', 'gtalk', 'mobile' etc. + type: string + example: gtalk + primary: + type: boolean + description: Denotes if this is the preferred messaging addressed among others, if any + description: See section 4.1.2 of RFC 7643 + Photo: + type: object + properties: + value: + description: URI of a photo of the User. + type: string + example: https://pics.nsfw.com/gossow.png + display: + description: A human readable name, primarily used for display purposes. + type: string + type: + description: A label indicating the attribute's function; e.g., 'photo' or 'thumbnail'. + type: string + example: thumbnail + primary: + type: boolean + description: Denotes if this is the preferred photo among others, if any + description: Points to a resource location representing the user's image. See section 4.1.2 of RFC 7643 + Address: + type: object + properties: + formatted: + type: string + description: The full mailing address, formatted for display or use with a mailing label. + streetAddress: + description: The full street address component, which may include house number, street name,PO BOX,etc. + type: string + example: 56 Acacia Avenue + locality: + type: string + description: City or locality of the address + region: + type: string + description: State or region of the address + postalCode: + type: string + description: Zip code + country: + type: string + description: Country expressed in ISO 3166-1 "alpha-2" code format + example: UK + type: + description: A label indicating the attribute's function; e.g., 'work' or 'home'. + type: string + example: home + primary: + type: boolean + description: Denotes if this is the preferred address among others, if any + description: Physical mailing address for this user. See section 4.1.2 of RFC 7643 + Role: + type: object + properties: + value: + description: The value of a role + type: string + example: Project manager + display: + description: A human readable name, primarily used for display purposes. + type: string + type: + description: A label indicating the attribute's function. + type: string + primary: + type: boolean + description: Denotes if this is the preferred role among others, if any + description: See section 4.1.2 of RFC 7643 + BaseResource: + type: object + properties: + schemas: + type: array + description: URIs that are used to indicate the namespaces of the SCIM schemas that define the attributes present in the current structure + items: + type: string + id: + type: string + description: A unique identifier for a SCIM resource. See section 3.1 of RFC 7643 + meta: + $ref: '#/components/schemas/Meta' + Group: + type: object + properties: + value: + type: string + description: Group identifier + example: 180ee84f0671b1 + $ref: + type: string + description: URI associated to the group + example: https://nsfw.com/scim/restv1/v2/Groups/180ee84f0671b1 + display: + description: A human readable name, primarily used for display purposes. + type: string + example: Cult managers + type: + type: string + description: Describes how the group membership was derived + example: direct + description: See section 4.1.2 of RFC 7643 + Entitlement: + type: object + properties: + value: + description: The value of an entitlement. + type: string + example: Stakeholder + display: + description: A human readable name, primarily used for display purposes. + type: string + type: + description: A label indicating the attribute's function. + type: string + primary: + type: boolean + description: Denotes if this is the preferred entitlement among others, if any + description: Entitlements represent things a user has, like rights. See section 4.1.2 of RFC 7643 + X509Certificate: + type: object + properties: + value: + description: The value of a X509 certificate. + type: string + display: + description: A human readable name, primarily used for display purposes. + type: string + type: + description: A label indicating the attribute's function. + type: string + primary: + type: boolean + description: Denotes if this is the preferred certificate among others, if any + description: A certificate associated with the user. See section 4.1.2 of RFC 7643 + Meta: + type: object + properties: + resourceType: + type: string + created: + type: string + lastModified: + type: string + location: + type: string + description: See section 3.1 of RFC 7643 + + ScimPatchOperation: + required: + - op + type: object + properties: + op: + type: string + description: The kind of operation to perform + enum: + - add + - remove + - replace + path: + type: string + description: Required when op is remove, optional otherwise + value: + $ref: '#/components/schemas/AnyValue' + description: Only required when op is add or replace + description: See section 3.5.2 of RFC 7644 + + ScimPatchRequest: + description: Stores one or more patch operations + required: + - operations + type: object + properties: + schemas: + type: array + items: + type: string + example: urn:ietf:params:scim:api:messages:2.0:PatchOp + operations: + type: array + items: + $ref: '#/components/schemas/ScimPatchOperation' + AnyValue: + description: Can be any value - string, number, boolean, array or object + + AuthHealthStatus: + type: object + description: Auth Server health data + additionalProperties: + type: string + + AdminRole: + type: object + description: Admin role + required: + - role + properties: + role: + type: string + description: role + description: + type: string + description: role description + deletable: + type: boolean + description: can we delete the role? + AdminPermission: + type: object + description: Admin permission + required: + - permission + properties: + permission: + type: string + description: permission + description: + type: string + description: permission description + RolePermissionMapping: + type: object + description: Admin role-permission mapping + required: + - role + properties: + role: + type: string + description: role + permissions: + type: array + items: + type: string + description: permissions + LicenseDetailsRequest: + type: object + description: Admin license details request + required: + - validityPeriod + properties: + validityPeriod: + type: string + description: The license will expire on following date. + maxActivations: + type: string + description: The maximum allowed activations of this license on different machines. + licenseActive: + type: string + description: Is license active? + LicenseApiRequest: + type: object + description: Admin license api request + required: + - licenseKey + properties: + licenseKey: + type: string + description: The license-key. + LicenseSpringCredentials: + type: object + required: + - apiKey + - productCode + - sharedKey + - managementKey + properties: + apiKey: + type: string + description: The api-key. + productCode: + type: string + description: The product-code. + sharedKey: + type: string + description: The shared-key. + managementKey: + type: string + description: The management-key. + LicenseDetailsResponse: + type: object + description: Admin license details response + properties: + licenseEnabled: + type: boolean + description: Is license module enabled in admin-ui application? + default: false + productName: + type: string + description: The license is registered under following product. + productCode: + type: string + description: The short code is used in our API calls in order to identify the product. + licenseType: + type: string + description: The type of license (eg Perpetual, Time-based, Subscription, and Consumption-based licenses). + maxActivations: + type: integer + description: The license key. + licenseKey: + type: string + description: The license key. + licenseActive: + type: boolean + description: Is license active? + default: false + validityPeriod: + type: string + description: The license validity period + companyName: + type: string + description: The company name of the registered license. + customerEmail: + type: string + description: The customer email address of the registered license. + customerFirstName: + type: string + description: The customer first name. + customerLastName: + type: string + description: The customer last name. + LicenseApiResponse: + type: object + properties: + apiResult: + type: boolean + description: liceseSpring api request success status + responseMessage: + type: string + description: Response Message + responseCode: + type: integer + description: Response code + ScimAppConfiguration: + type: object + properties: + baseDN: + type: string + description: Application config Base DN + applicationUrl: + type: string + description: Application base URL + baseEndpoint: + type: string + description: SCIM base endpoint URL + personCustomObjectClass: + type: string + description: Person Object Class + oxAuthIssuer: + type: string + description: Jans Auth - Issuer identifier. + protectionMode: + type: string + enum: + - OAUTH + - BYPASS + description: SCIM Protection Mode + maxCount: + type: integer + example: Maximum number of results per page + userExtensionSchemaURI: + type: string + description: User Extension Schema URI + loggingLevel: + type: string + description: Logging level for scim logger. + enum: + - TRACE + - DEBUG + - INFO + - WARN + - ERROR + - FATAL + - OFF + loggingLayout: + type: string + description: Logging layout used for Server loggers. + externalLoggerConfiguration: + type: string + description: Path to external log4j2 logging configuration. + metricReporterInterval: + type: integer + description: The interval for metric reporter in seconds. + metricReporterKeepDataDays: + type: integer + description: The days to keep metric reported data. + metricReporterEnabled: + type: boolean + description: Metric reported data enabled flag. + disableJdkLogger: + type: boolean + description: Boolean value specifying whether to enable JDK Loggers. + useLocalCache: + type: boolean + description: Boolean value specifying whether to enable local in-memory cache. + + Organization: + type: object + properties: + displayName: + type: string + description: Organization name + description: + type: string + description: Organization description + member: + type: string + description: String describing memberOf + countryName: + type: string + description: Organization country name + organization: + type: string + status: + type: string + managerGroup: + type: string + description: qualified id of the group + example: inum=60B7,ou=groups,o=jans + themeColor: + type: string + description: color of the theme + example: 166309 + shortName: + type: string + customMessages: + type: array + items: + type: string + title: + type: string + jsLogoPath: + type: string + description: Path to organization logo image + jsFaviconPath: + type: string + description: Path to organization favicon image + + + FacterData: + type: object + properties: + memoryfree: + type: string + description: Server free memory + swapfree: + type: string + description: Server swap free + hostname: + type: string + description: Server hostname + ipaddress: + type: string + description: Server ipaddress + uptime: + type: string + description: Server uptime + free_disk_space: + type: string + description: Server free disk space + load_average: + type: string + description: Server average load time + + + StatsData: + type: object + properties: + dbType: + type: string + description: Jans Server DB type + lastUpdate: + type: string + description: Stats update time + facterData: + type: object + $ref: '#/components/schemas/FacterData' + description: Underlying Server stats + + CustomUser: + title: User object + description: User. + type: object + required: + - userId + - mail + - displayName + - givenName + - jansStatus + properties: + dn: + type: string + description: Domain name. + baseDN: + type: string + description: Base DN for the User entity + jansStatus: + type: string + description: User status + enum: + - ACTIVE + - INACTIVE + - EXPIRED + - REGISTER + userId: + description: A domain issued and managed identifier for the user. + type: string + createdAt: + description: User creation date. + type: string + format: date-time + updatedAt: + description: Time the information of the person was last updated. Seconds from 1970-01-01T0:0:0Z + type: string + format: date-time + oxAuthPersistentJwt: + description: Persistent JWT. + type: array + items: + type: string + customAttributes: + description: dn of associated clients with the user. + type: array + items: + $ref: '#/components/schemas/CustomAttribute' + mail: + type: string + description: User mail + displayName: + type: string + description: Name of the user suitable for display to end-users + givenName: + type: string + description: User given Name + userPassword: + type: string + description: User password + inum: + description: XRI i-number. Identifier to uniquely identify the user. + type: string + + ExtendedCustomUser: + allOf: # Combines the CustomUser and the inline model + - $ref: '#/components/schemas/CustomUser' + - type: object + required: + - userPassword + properties: + userPassword: + type: string + description: User password + + UserPatchRequest: + title: User Patch Request object + description: UserPatchRequest. + type: object + properties: + jsonPatchString: + type: object + description: Possible errors assosiated with the script. + $ref: '#/components/schemas/PatchRequest' + customAttributes: + description: dn of associated clients with the user. + type: array + items: + $ref: '#/components/schemas/CustomAttribute' + + EngineConfig: + title: Engine config object + description: Engine config object that offers an alternative way to build authentication flows in Janssen server. + type: object + properties: + enabled: + type: boolean + description: boolean value indicating if agama configuration enabled. + default: false + templatesPath: + type: string + description: path to the templates + default: '/ftl' + scriptsPath: + type: string + description: path to the scripts + default: '/scripts' + serializerType: + type: string + description: type of supported serializer + default: KRYO + enum: + - KRYO + - FST + maxItemsLoggedInCollections: + type: integer + description: maximum logged in collection item + default: 3 + minimum: 1 + pageMismatchErrorPage: + type: string + description: mismatch error page. + default: mismatch.ftl + interruptionErrorPage: + type: string + description: interruption error page. + default: timeout.ftl + crashErrorPage: + type: string + description: crash error page. + default: crash.ftl + finishedFlowPage: + type: string + description: finished flow page. + default: finished.ftl + bridgeScriptPage: + type: string + description: bridge script page. + default: agama.xhtml + defaultResponseHeaders: + type: object + additionalProperties: + type: string + diff --git a/jans-cli-tui/cli/pylib/__init__.py b/jans-cli-tui/cli/pylib/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/jans-cli-tui/cli/pylib/tabulate/LICENSE b/jans-cli-tui/cli/pylib/tabulate/LICENSE new file mode 100644 index 00000000000..81241eca637 --- /dev/null +++ b/jans-cli-tui/cli/pylib/tabulate/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2011-2020 Sergey Astanin and contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/jans-cli-tui/cli/pylib/tabulate/README b/jans-cli-tui/cli/pylib/tabulate/README new file mode 100644 index 00000000000..42061c01a1c --- /dev/null +++ b/jans-cli-tui/cli/pylib/tabulate/README @@ -0,0 +1 @@ +README.md \ No newline at end of file diff --git a/jans-cli-tui/cli/pylib/tabulate/README.md b/jans-cli-tui/cli/pylib/tabulate/README.md new file mode 100644 index 00000000000..ce06dadc834 --- /dev/null +++ b/jans-cli-tui/cli/pylib/tabulate/README.md @@ -0,0 +1,747 @@ +python-tabulate +=============== + +Pretty-print tabular data in Python, a library and a command-line +utility. + +The main use cases of the library are: + +- printing small tables without hassle: just one function call, + formatting is guided by the data itself +- authoring tabular data for lightweight plain-text markup: multiple + output formats suitable for further editing or transformation +- readable presentation of mixed textual and numeric data: smart + column alignment, configurable number formatting, alignment by a + decimal point + +Installation +------------ + +To install the Python library and the command line utility, run: + + pip install tabulate + +The command line utility will be installed as `tabulate` to `bin` on +Linux (e.g. `/usr/bin`); or as `tabulate.exe` to `Scripts` in your +Python installation on Windows (e.g. +`C:\Python27\Scripts\tabulate.exe`). + +You may consider installing the library only for the current user: + + pip install tabulate --user + +In this case the command line utility will be installed to +`~/.local/bin/tabulate` on Linux and to +`%APPDATA%\Python\Scripts\tabulate.exe` on Windows. + +To install just the library on Unix-like operating systems: + + TABULATE_INSTALL=lib-only pip install tabulate + +On Windows: + + set TABULATE_INSTALL=lib-only + pip install tabulate + +Build status +------------ + +[![Build status](https://circleci.com/gh/astanin/python-tabulate.svg?style=svg)](https://circleci.com/gh/astanin/python-tabulate/tree/master) [![Build status](https://ci.appveyor.com/api/projects/status/8745yksvvol7h3d7/branch/master?svg=true)](https://ci.appveyor.com/project/astanin/python-tabulate/branch/master) + +Library usage +------------- + +The module provides just one function, `tabulate`, which takes a list of +lists or another tabular data type as the first argument, and outputs a +nicely formatted plain-text table: + + >>> from tabulate import tabulate + + >>> table = [["Sun",696000,1989100000],["Earth",6371,5973.6], + ... ["Moon",1737,73.5],["Mars",3390,641.85]] + >>> print(tabulate(table)) + ----- ------ ------------- + Sun 696000 1.9891e+09 + Earth 6371 5973.6 + Moon 1737 73.5 + Mars 3390 641.85 + ----- ------ ------------- + +The following tabular data types are supported: + +- list of lists or another iterable of iterables +- list or another iterable of dicts (keys as columns) +- dict of iterables (keys as columns) +- two-dimensional NumPy array +- NumPy record arrays (names as columns) +- pandas.DataFrame + +Examples in this file use Python2. Tabulate supports Python3 too. + +### Headers + +The second optional argument named `headers` defines a list of column +headers to be used: + + >>> print(tabulate(table, headers=["Planet","R (km)", "mass (x 10^29 kg)"])) + Planet R (km) mass (x 10^29 kg) + -------- -------- ------------------- + Sun 696000 1.9891e+09 + Earth 6371 5973.6 + Moon 1737 73.5 + Mars 3390 641.85 + +If `headers="firstrow"`, then the first row of data is used: + + >>> print(tabulate([["Name","Age"],["Alice",24],["Bob",19]], + ... headers="firstrow")) + Name Age + ------ ----- + Alice 24 + Bob 19 + +If `headers="keys"`, then the keys of a dictionary/dataframe, or column +indices are used. It also works for NumPy record arrays and lists of +dictionaries or named tuples: + + >>> print(tabulate({"Name": ["Alice", "Bob"], + ... "Age": [24, 19]}, headers="keys")) + Age Name + ----- ------ + 24 Alice + 19 Bob + +### Row Indices + +By default, only pandas.DataFrame tables have an additional column +called row index. To add a similar column to any other type of table, +pass `showindex="always"` or `showindex=True` argument to `tabulate()`. +To suppress row indices for all types of data, pass `showindex="never"` +or `showindex=False`. To add a custom row index column, pass +`showindex=rowIDs`, where `rowIDs` is some iterable: + + >>> print(tabulate([["F",24],["M",19]], showindex="always")) + - - -- + 0 F 24 + 1 M 19 + - - -- + +### Table format + +There is more than one way to format a table in plain text. The third +optional argument named `tablefmt` defines how the table is formatted. + +Supported table formats are: + +- "plain" +- "simple" +- "github" +- "grid" +- "fancy\_grid" +- "pipe" +- "orgtbl" +- "jira" +- "presto" +- "pretty" +- "psql" +- "rst" +- "mediawiki" +- "moinmoin" +- "youtrack" +- "html" +- "unsafehtml" +- "latex" +- "latex\_raw" +- "latex\_booktabs" +- "textile" + +`plain` tables do not use any pseudo-graphics to draw lines: + + >>> table = [["spam",42],["eggs",451],["bacon",0]] + >>> headers = ["item", "qty"] + >>> print(tabulate(table, headers, tablefmt="plain")) + item qty + spam 42 + eggs 451 + bacon 0 + +`simple` is the default format (the default may change in future +versions). It corresponds to `simple_tables` in [Pandoc Markdown +extensions](http://johnmacfarlane.net/pandoc/README.html#tables): + + >>> print(tabulate(table, headers, tablefmt="simple")) + item qty + ------ ----- + spam 42 + eggs 451 + bacon 0 + +`github` follows the conventions of Github flavored Markdown. It +corresponds to the `pipe` format without alignment colons: + + >>> print(tabulate(table, headers, tablefmt="github")) + | item | qty | + |--------|-------| + | spam | 42 | + | eggs | 451 | + | bacon | 0 | + +`grid` is like tables formatted by Emacs' +[table.el](http://table.sourceforge.net/) package. It corresponds to +`grid_tables` in Pandoc Markdown extensions: + + >>> print(tabulate(table, headers, tablefmt="grid")) + +--------+-------+ + | item | qty | + +========+=======+ + | spam | 42 | + +--------+-------+ + | eggs | 451 | + +--------+-------+ + | bacon | 0 | + +--------+-------+ + +`fancy_grid` draws a grid using box-drawing characters: + + >>> print(tabulate(table, headers, tablefmt="fancy_grid")) + ╒════════╤═══════╕ + │ item │ qty │ + ╞════════╪═══════╡ + │ spam │ 42 │ + ├────────┼───────┤ + │ eggs │ 451 │ + ├────────┼───────┤ + │ bacon │ 0 │ + ╘════════╧═══════╛ + +`presto` is like tables formatted by Presto cli: + + >>> print(tabulate(table, headers, tablefmt="presto")) + item | qty + --------+------- + spam | 42 + eggs | 451 + bacon | 0 + +`pretty` attempts to be close to the format emitted by the PrettyTables +library: + + >>> print(tabulate(table, headers, tablefmt="pretty")) + +-------+-----+ + | item | qty | + +-------+-----+ + | spam | 42 | + | eggs | 451 | + | bacon | 0 | + +-------+-----+ + +`psql` is like tables formatted by Postgres' psql cli: + + >>> print(tabulate(table, headers, tablefmt="psql")) + +--------+-------+ + | item | qty | + |--------+-------| + | spam | 42 | + | eggs | 451 | + | bacon | 0 | + +--------+-------+ + +`pipe` follows the conventions of [PHP Markdown +Extra](http://michelf.ca/projects/php-markdown/extra/#table) extension. +It corresponds to `pipe_tables` in Pandoc. This format uses colons to +indicate column alignment: + + >>> print(tabulate(table, headers, tablefmt="pipe")) + | item | qty | + |:-------|------:| + | spam | 42 | + | eggs | 451 | + | bacon | 0 | + +`orgtbl` follows the conventions of Emacs +[org-mode](http://orgmode.org/manual/Tables.html), and is editable also +in the minor orgtbl-mode. Hence its name: + + >>> print(tabulate(table, headers, tablefmt="orgtbl")) + | item | qty | + |--------+-------| + | spam | 42 | + | eggs | 451 | + | bacon | 0 | + +`jira` follows the conventions of Atlassian Jira markup language: + + >>> print(tabulate(table, headers, tablefmt="jira")) + || item || qty || + | spam | 42 | + | eggs | 451 | + | bacon | 0 | + +`rst` formats data like a simple table of the +[reStructuredText](http://docutils.sourceforge.net/docs/user/rst/quickref.html#tables) +format: + + >>> print(tabulate(table, headers, tablefmt="rst")) + ====== ===== + item qty + ====== ===== + spam 42 + eggs 451 + bacon 0 + ====== ===== + +`mediawiki` format produces a table markup used in +[Wikipedia](http://www.mediawiki.org/wiki/Help:Tables) and on other +MediaWiki-based sites: + + >>> print(tabulate(table, headers, tablefmt="mediawiki")) + {| class="wikitable" style="text-align: left;" + |+ + |- + ! item !! align="right"| qty + |- + | spam || align="right"| 42 + |- + | eggs || align="right"| 451 + |- + | bacon || align="right"| 0 + |} + +`moinmoin` format produces a table markup used in +[MoinMoin](https://moinmo.in/) wikis: + + >>> print(tabulate(table, headers, tablefmt="moinmoin")) + || ''' item ''' || ''' quantity ''' || + || spam || 41.999 || + || eggs || 451 || + || bacon || || + +`youtrack` format produces a table markup used in Youtrack tickets: + + >>> print(tabulate(table, headers, tablefmt="youtrack")) + || item || quantity || + | spam | 41.999 | + | eggs | 451 | + | bacon | | + +`textile` format produces a table markup used in +[Textile](http://redcloth.org/hobix.com/textile/) format: + + >>> print(tabulate(table, headers, tablefmt="textile")) + |_. item |_. qty | + |<. spam |>. 42 | + |<. eggs |>. 451 | + |<. bacon |>. 0 | + +`html` produces standard HTML markup as an html.escape'd str +with a ._repr_html_ method so that Jupyter Lab and Notebook display the HTML +and a .str property so that the raw HTML remains accessible. +`unsafehtml` table format can be used if an unescaped HTML is required: + + >>> print(tabulate(table, headers, tablefmt="html")) + + + + + + + +
item qty
spam 42
eggs 451
bacon 0
+ +`latex` format creates a `tabular` environment for LaTeX markup, +replacing special characters like `_` or `\` to their LaTeX +correspondents: + + >>> print(tabulate(table, headers, tablefmt="latex")) + \begin{tabular}{lr} + \hline + item & qty \\ + \hline + spam & 42 \\ + eggs & 451 \\ + bacon & 0 \\ + \hline + \end{tabular} + +`latex_raw` behaves like `latex` but does not escape LaTeX commands and +special characters. + +`latex_booktabs` creates a `tabular` environment for LaTeX markup using +spacing and style from the `booktabs` package. + +### Column alignment + +`tabulate` is smart about column alignment. It detects columns which +contain only numbers, and aligns them by a decimal point (or flushes +them to the right if they appear to be integers). Text columns are +flushed to the left. + +You can override the default alignment with `numalign` and `stralign` +named arguments. Possible column alignments are: `right`, `center`, +`left`, `decimal` (only for numbers), and `None` (to disable alignment). + +Aligning by a decimal point works best when you need to compare numbers +at a glance: + + >>> print(tabulate([[1.2345],[123.45],[12.345],[12345],[1234.5]])) + ---------- + 1.2345 + 123.45 + 12.345 + 12345 + 1234.5 + ---------- + +Compare this with a more common right alignment: + + >>> print(tabulate([[1.2345],[123.45],[12.345],[12345],[1234.5]], numalign="right")) + ------ + 1.2345 + 123.45 + 12.345 + 12345 + 1234.5 + ------ + +For `tabulate`, anything which can be parsed as a number is a number. +Even numbers represented as strings are aligned properly. This feature +comes in handy when reading a mixed table of text and numbers from a +file: + + >>> import csv ; from StringIO import StringIO + >>> table = list(csv.reader(StringIO("spam, 42\neggs, 451\n"))) + >>> table + [['spam', ' 42'], ['eggs', ' 451']] + >>> print(tabulate(table)) + ---- ---- + spam 42 + eggs 451 + ---- ---- + + +To disable this feature use `disable_numparse=True`. + + >>> print(tabulate.tabulate([["Ver1", "18.0"], ["Ver2","19.2"]], tablefmt="simple", disable_numparse=True)) + ---- ---- + Ver1 18.0 + Ver2 19.2 + ---- ---- + + +### Custom column alignment + +`tabulate` allows a custom column alignment to override the above. The +`colalign` argument can be a list or a tuple of `stralign` named +arguments. Possible column alignments are: `right`, `center`, `left`, +`decimal` (only for numbers), and `None` (to disable alignment). +Omitting an alignment uses the default. For example: + + >>> print(tabulate([["one", "two"], ["three", "four"]], colalign=("right",)) + ----- ---- + one two + three four + ----- ---- + +### Number formatting + +`tabulate` allows to define custom number formatting applied to all +columns of decimal numbers. Use `floatfmt` named argument: + + >>> print(tabulate([["pi",3.141593],["e",2.718282]], floatfmt=".4f")) + -- ------ + pi 3.1416 + e 2.7183 + -- ------ + +`floatfmt` argument can be a list or a tuple of format strings, one per +column, in which case every column may have different number formatting: + + >>> print(tabulate([[0.12345, 0.12345, 0.12345]], floatfmt=(".1f", ".3f"))) + --- ----- ------- + 0.1 0.123 0.12345 + --- ----- ------- + +### Text formatting + +By default, `tabulate` removes leading and trailing whitespace from text +columns. To disable whitespace removal, set the global module-level flag +`PRESERVE_WHITESPACE`: + + import tabulate + tabulate.PRESERVE_WHITESPACE = True + +### Wide (fullwidth CJK) symbols + +To properly align tables which contain wide characters (typically +fullwidth glyphs from Chinese, Japanese or Korean languages), the user +should install `wcwidth` library. To install it together with +`tabulate`: + + pip install tabulate[widechars] + +Wide character support is enabled automatically if `wcwidth` library is +already installed. To disable wide characters support without +uninstalling `wcwidth`, set the global module-level flag +`WIDE_CHARS_MODE`: + + import tabulate + tabulate.WIDE_CHARS_MODE = False + +### Multiline cells + +Most table formats support multiline cell text (text containing newline +characters). The newline characters are honored as line break +characters. + +Multiline cells are supported for data rows and for header rows. + +Further automatic line breaks are not inserted. Of course, some output +formats such as latex or html handle automatic formatting of the cell +content on their own, but for those that don't, the newline characters +in the input cell text are the only means to break a line in cell text. + +Note that some output formats (e.g. simple, or plain) do not represent +row delimiters, so that the representation of multiline cells in such +formats may be ambiguous to the reader. + +The following examples of formatted output use the following table with +a multiline cell, and headers with a multiline cell: + + >>> table = [["eggs",451],["more\nspam",42]] + >>> headers = ["item\nname", "qty"] + +`plain` tables: + + >>> print(tabulate(table, headers, tablefmt="plain")) + item qty + name + eggs 451 + more 42 + spam + +`simple` tables: + + >>> print(tabulate(table, headers, tablefmt="simple")) + item qty + name + ------ ----- + eggs 451 + more 42 + spam + +`grid` tables: + + >>> print(tabulate(table, headers, tablefmt="grid")) + +--------+-------+ + | item | qty | + | name | | + +========+=======+ + | eggs | 451 | + +--------+-------+ + | more | 42 | + | spam | | + +--------+-------+ + +`fancy_grid` tables: + + >>> print(tabulate(table, headers, tablefmt="fancy_grid")) + ╒════════╤═══════╕ + │ item │ qty │ + │ name │ │ + ╞════════╪═══════╡ + │ eggs │ 451 │ + ├────────┼───────┤ + │ more │ 42 │ + │ spam │ │ + ╘════════╧═══════╛ + +`pipe` tables: + + >>> print(tabulate(table, headers, tablefmt="pipe")) + | item | qty | + | name | | + |:-------|------:| + | eggs | 451 | + | more | 42 | + | spam | | + +`orgtbl` tables: + + >>> print(tabulate(table, headers, tablefmt="orgtbl")) + | item | qty | + | name | | + |--------+-------| + | eggs | 451 | + | more | 42 | + | spam | | + +`jira` tables: + + >>> print(tabulate(table, headers, tablefmt="jira")) + | item | qty | + | name | | + |:-------|------:| + | eggs | 451 | + | more | 42 | + | spam | | + +`presto` tables: + + >>> print(tabulate(table, headers, tablefmt="presto")) + item | qty + name | + --------+------- + eggs | 451 + more | 42 + spam | + +`pretty` tables: + + >>> print(tabulate(table, headers, tablefmt="pretty")) + +------+-----+ + | item | qty | + | name | | + +------+-----+ + | eggs | 451 | + | more | 42 | + | spam | | + +------+-----+ + +`psql` tables: + + >>> print(tabulate(table, headers, tablefmt="psql")) + +--------+-------+ + | item | qty | + | name | | + |--------+-------| + | eggs | 451 | + | more | 42 | + | spam | | + +--------+-------+ + +`rst` tables: + + >>> print(tabulate(table, headers, tablefmt="rst")) + ====== ===== + item qty + name + ====== ===== + eggs 451 + more 42 + spam + ====== ===== + +Multiline cells are not well supported for the other table formats. + +Usage of the command line utility +--------------------------------- + + Usage: tabulate [options] [FILE ...] + + FILE a filename of the file with tabular data; + if "-" or missing, read data from stdin. + + Options: + + -h, --help show this message + -1, --header use the first row of data as a table header + -o FILE, --output FILE print table to FILE (default: stdout) + -s REGEXP, --sep REGEXP use a custom column separator (default: whitespace) + -F FPFMT, --float FPFMT floating point number format (default: g) + -f FMT, --format FMT set output table format; supported formats: + plain, simple, github, grid, fancy_grid, pipe, + orgtbl, rst, mediawiki, html, latex, latex_raw, + latex_booktabs, tsv + (default: simple) + +Performance considerations +-------------------------- + +Such features as decimal point alignment and trying to parse everything +as a number imply that `tabulate`: + +- has to "guess" how to print a particular tabular data type +- needs to keep the entire table in-memory +- has to "transpose" the table twice +- does much more work than it may appear + +It may not be suitable for serializing really big tables (but who's +going to do that, anyway?) or printing tables in performance sensitive +applications. `tabulate` is about two orders of magnitude slower than +simply joining lists of values with a tab, coma or other separator. + +In the same time `tabulate` is comparable to other table +pretty-printers. Given a 10x10 table (a list of lists) of mixed text and +numeric data, `tabulate` appears to be slower than `asciitable`, and +faster than `PrettyTable` and `texttable` The following mini-benchmark +was run in Python 3.8.1 in Windows 10 x64: + + =========================== ========== =========== + Table formatter time, μs rel. time + =========================== ========== =========== + csv to StringIO 12.4 1.0 + join with tabs and newlines 15.7 1.3 + asciitable (0.8.0) 208.3 16.7 + tabulate (0.8.7) 492.1 39.5 + PrettyTable (0.7.2) 945.5 76.0 + texttable (1.6.2) 1239.5 99.6 + =========================== ========== =========== + + +Version history +--------------- + +The full version history can be found at the [changelog](https://github.com/astanin/python-tabulate/blob/master/CHANGELOG). + +How to contribute +----------------- + +Contributions should include tests and an explanation for the changes +they propose. Documentation (examples, docstrings, README.md) should be +updated accordingly. + +This project uses [nose](https://nose.readthedocs.org/) testing +framework and [tox](https://tox.readthedocs.io/) to automate testing in +different environments. Add tests to one of the files in the `test/` +folder. + +To run tests on all supported Python versions, make sure all Python +interpreters, `nose` and `tox` are installed, then run `tox` in the root +of the project source tree. + +On Linux `tox` expects to find executables like `python2.6`, +`python2.7`, `python3.4` etc. On Windows it looks for +`C:\Python26\python.exe`, `C:\Python27\python.exe` and +`C:\Python34\python.exe` respectively. + +To test only some Python environements, use `-e` option. For example, to +test only against Python 2.7 and Python 3.6, run: + + tox -e py27,py36 + +in the root of the project source tree. + +To enable NumPy and Pandas tests, run: + + tox -e py27-extra,py36-extra + +(this may take a long time the first time, because NumPy and Pandas will +have to be installed in the new virtual environments) + +See `tox.ini` file to learn how to use `nosetests` directly to test +individual Python versions. + +Contributors +------------ + +Sergey Astanin, Pau Tallada Crespí, Erwin Marsi, Mik Kocikowski, Bill +Ryder, Zach Dwiel, Frederik Rietdijk, Philipp Bogensberger, Greg +(anonymous), Stefan Tatschner, Emiel van Miltenburg, Brandon Bennett, +Amjith Ramanujam, Jan Schulz, Simon Percivall, Javier Santacruz +López-Cepero, Sam Denton, Alexey Ziyangirov, acaird, Cesar Sanchez, +naught101, John Vandenberg, Zack Dever, Christian Clauss, Benjamin +Maier, Andy MacKinlay, Thomas Roten, Jue Wang, Joe King, Samuel Phan, +Nick Satterly, Daniel Robbins, Dmitry B, Lars Butler, Andreas Maier, +Dick Marinus, Sébastien Celles, Yago González, Andrew Gaul, Wim Glenn, +Jean Michel Rouly, Tim Gates, John Vandenberg, Sorin Sbarnea, +Wes Turner, Andrew Tija, Marco Gorelli, Sean McGinnis, danja100. diff --git a/jans-cli-tui/cli/pylib/tabulate/__init__.py b/jans-cli-tui/cli/pylib/tabulate/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/jans-cli-tui/cli/pylib/tabulate/tabulate.py b/jans-cli-tui/cli/pylib/tabulate/tabulate.py new file mode 100644 index 00000000000..2bbb913c62d --- /dev/null +++ b/jans-cli-tui/cli/pylib/tabulate/tabulate.py @@ -0,0 +1,1792 @@ +# -*- coding: utf-8 -*- + +"""Pretty-print tabular data.""" + +from __future__ import print_function +from __future__ import unicode_literals +from collections import namedtuple +from platform import python_version_tuple +import re +import math +import sys + + +if sys.version_info.major >= 3 and sys.version_info.minor >= 3: + from collections.abc import Iterable +else: + from collections import Iterable + + +if python_version_tuple()[0] < "3": + from itertools import izip_longest + from functools import partial + + _none_type = type(None) + _bool_type = bool + _int_type = int + _long_type = long # noqa + _float_type = float + _text_type = unicode # noqa + _binary_type = str + + def _is_file(f): + return hasattr(f, "read") + + +else: + from itertools import zip_longest as izip_longest + from functools import reduce, partial + + _none_type = type(None) + _bool_type = bool + _int_type = int + _long_type = int + _float_type = float + _text_type = str + _binary_type = bytes + basestring = str + + import io + + def _is_file(f): + return isinstance(f, io.IOBase) + + +try: + import wcwidth # optional wide-character (CJK) support +except ImportError: + wcwidth = None + +try: + from html import escape as htmlescape +except ImportError: + from cgi import escape as htmlescape + + +__all__ = ["tabulate", "tabulate_formats", "simple_separated_format"] +__version__ = "0.8.8" + + +# minimum extra space in headers +MIN_PADDING = 2 + +# Whether or not to preserve leading/trailing whitespace in data. +PRESERVE_WHITESPACE = False + +_DEFAULT_FLOATFMT = "g" +_DEFAULT_MISSINGVAL = "" + + +# if True, enable wide-character (CJK) support +WIDE_CHARS_MODE = wcwidth is not None + + +Line = namedtuple("Line", ["begin", "hline", "sep", "end"]) + + +DataRow = namedtuple("DataRow", ["begin", "sep", "end"]) + + +# A table structure is suppposed to be: +# +# --- lineabove --------- +# headerrow +# --- linebelowheader --- +# datarow +# --- linebewteenrows --- +# ... (more datarows) ... +# --- linebewteenrows --- +# last datarow +# --- linebelow --------- +# +# TableFormat's line* elements can be +# +# - either None, if the element is not used, +# - or a Line tuple, +# - or a function: [col_widths], [col_alignments] -> string. +# +# TableFormat's *row elements can be +# +# - either None, if the element is not used, +# - or a DataRow tuple, +# - or a function: [cell_values], [col_widths], [col_alignments] -> string. +# +# padding (an integer) is the amount of white space around data values. +# +# with_header_hide: +# +# - either None, to display all table elements unconditionally, +# - or a list of elements not to be displayed if the table has column headers. +# +TableFormat = namedtuple( + "TableFormat", + [ + "lineabove", + "linebelowheader", + "linebetweenrows", + "linebelow", + "headerrow", + "datarow", + "padding", + "with_header_hide", + ], +) + + +def _pipe_segment_with_colons(align, colwidth): + """Return a segment of a horizontal line with optional colons which + indicate column's alignment (as in `pipe` output format).""" + w = colwidth + if align in ["right", "decimal"]: + return ("-" * (w - 1)) + ":" + elif align == "center": + return ":" + ("-" * (w - 2)) + ":" + elif align == "left": + return ":" + ("-" * (w - 1)) + else: + return "-" * w + + +def _pipe_line_with_colons(colwidths, colaligns): + """Return a horizontal line with optional colons to indicate column's + alignment (as in `pipe` output format).""" + if not colaligns: # e.g. printing an empty data frame (github issue #15) + colaligns = [""] * len(colwidths) + segments = [_pipe_segment_with_colons(a, w) for a, w in zip(colaligns, colwidths)] + return "|" + "|".join(segments) + "|" + + +def _mediawiki_row_with_attrs(separator, cell_values, colwidths, colaligns): + alignment = { + "left": "", + "right": 'align="right"| ', + "center": 'align="center"| ', + "decimal": 'align="right"| ', + } + # hard-coded padding _around_ align attribute and value together + # rather than padding parameter which affects only the value + values_with_attrs = [ + " " + alignment.get(a, "") + c + " " for c, a in zip(cell_values, colaligns) + ] + colsep = separator * 2 + return (separator + colsep.join(values_with_attrs)).rstrip() + + +def _textile_row_with_attrs(cell_values, colwidths, colaligns): + cell_values[0] += " " + alignment = {"left": "<.", "right": ">.", "center": "=.", "decimal": ">."} + values = (alignment.get(a, "") + v for a, v in zip(colaligns, cell_values)) + return "|" + "|".join(values) + "|" + + +def _html_begin_table_without_header(colwidths_ignore, colaligns_ignore): + # this table header will be suppressed if there is a header row + return "\n" + + +def _html_row_with_attrs(celltag, unsafe, cell_values, colwidths, colaligns): + alignment = { + "left": "", + "right": ' style="text-align: right;"', + "center": ' style="text-align: center;"', + "decimal": ' style="text-align: right;"', + } + if unsafe: + values_with_attrs = [ + "<{0}{1}>{2}".format(celltag, alignment.get(a, ""), c) + for c, a in zip(cell_values, colaligns) + ] + else: + values_with_attrs = [ + "<{0}{1}>{2}".format(celltag, alignment.get(a, ""), htmlescape(c)) + for c, a in zip(cell_values, colaligns) + ] + rowhtml = "{}".format("".join(values_with_attrs).rstrip()) + if celltag == "th": # it's a header row, create a new table header + rowhtml = "
\n\n{}\n\n".format(rowhtml) + return rowhtml + + +def _moin_row_with_attrs(celltag, cell_values, colwidths, colaligns, header=""): + alignment = { + "left": "", + "right": '', + "center": '', + "decimal": '', + } + values_with_attrs = [ + "{0}{1} {2} ".format(celltag, alignment.get(a, ""), header + c + header) + for c, a in zip(cell_values, colaligns) + ] + return "".join(values_with_attrs) + "||" + + +def _latex_line_begin_tabular(colwidths, colaligns, booktabs=False): + alignment = {"left": "l", "right": "r", "center": "c", "decimal": "r"} + tabular_columns_fmt = "".join([alignment.get(a, "l") for a in colaligns]) + return "\n".join( + [ + "\\begin{tabular}{" + tabular_columns_fmt + "}", + "\\toprule" if booktabs else "\\hline", + ] + ) + + +LATEX_ESCAPE_RULES = { + r"&": r"\&", + r"%": r"\%", + r"$": r"\$", + r"#": r"\#", + r"_": r"\_", + r"^": r"\^{}", + r"{": r"\{", + r"}": r"\}", + r"~": r"\textasciitilde{}", + "\\": r"\textbackslash{}", + r"<": r"\ensuremath{<}", + r">": r"\ensuremath{>}", +} + + +def _latex_row(cell_values, colwidths, colaligns, escrules=LATEX_ESCAPE_RULES): + def escape_char(c): + return escrules.get(c, c) + + escaped_values = ["".join(map(escape_char, cell)) for cell in cell_values] + rowfmt = DataRow("", "&", "\\\\") + return _build_simple_row(escaped_values, rowfmt) + + +def _rst_escape_first_column(rows, headers): + def escape_empty(val): + if isinstance(val, (_text_type, _binary_type)) and not val.strip(): + return ".." + else: + return val + + new_headers = list(headers) + new_rows = [] + if headers: + new_headers[0] = escape_empty(headers[0]) + for row in rows: + new_row = list(row) + if new_row: + new_row[0] = escape_empty(row[0]) + new_rows.append(new_row) + return new_rows, new_headers + + +_table_formats = { + "simple": TableFormat( + lineabove=Line("", "-", " ", ""), + linebelowheader=Line("", "-", " ", ""), + linebetweenrows=None, + linebelow=Line("", "-", " ", ""), + headerrow=DataRow("", " ", ""), + datarow=DataRow("", " ", ""), + padding=0, + with_header_hide=["lineabove", "linebelow"], + ), + "plain": TableFormat( + lineabove=None, + linebelowheader=None, + linebetweenrows=None, + linebelow=None, + headerrow=DataRow("", " ", ""), + datarow=DataRow("", " ", ""), + padding=0, + with_header_hide=None, + ), + "grid": TableFormat( + lineabove=Line("+", "-", "+", "+"), + linebelowheader=Line("+", "=", "+", "+"), + linebetweenrows=Line("+", "-", "+", "+"), + linebelow=Line("+", "-", "+", "+"), + headerrow=DataRow("|", "|", "|"), + datarow=DataRow("|", "|", "|"), + padding=1, + with_header_hide=None, + ), + "fancy_grid": TableFormat( + lineabove=Line("╒", "═", "╤", "╕"), + linebelowheader=Line("╞", "═", "╪", "╡"), + linebetweenrows=Line("├", "─", "┼", "┤"), + linebelow=Line("╘", "═", "╧", "╛"), + headerrow=DataRow("│", "│", "│"), + datarow=DataRow("│", "│", "│"), + padding=1, + with_header_hide=None, + ), + "github": TableFormat( + lineabove=Line("|", "-", "|", "|"), + linebelowheader=Line("|", "-", "|", "|"), + linebetweenrows=None, + linebelow=None, + headerrow=DataRow("|", "|", "|"), + datarow=DataRow("|", "|", "|"), + padding=1, + with_header_hide=["lineabove"], + ), + "pipe": TableFormat( + lineabove=_pipe_line_with_colons, + linebelowheader=_pipe_line_with_colons, + linebetweenrows=None, + linebelow=None, + headerrow=DataRow("|", "|", "|"), + datarow=DataRow("|", "|", "|"), + padding=1, + with_header_hide=["lineabove"], + ), + "orgtbl": TableFormat( + lineabove=None, + linebelowheader=Line("|", "-", "+", "|"), + linebetweenrows=None, + linebelow=None, + headerrow=DataRow("|", "|", "|"), + datarow=DataRow("|", "|", "|"), + padding=1, + with_header_hide=None, + ), + "jira": TableFormat( + lineabove=None, + linebelowheader=None, + linebetweenrows=None, + linebelow=None, + headerrow=DataRow("||", "||", "||"), + datarow=DataRow("|", "|", "|"), + padding=1, + with_header_hide=None, + ), + "presto": TableFormat( + lineabove=None, + linebelowheader=Line("", "-", "+", ""), + linebetweenrows=None, + linebelow=None, + headerrow=DataRow("", "|", ""), + datarow=DataRow("", "|", ""), + padding=1, + with_header_hide=None, + ), + "pretty": TableFormat( + lineabove=Line("+", "-", "+", "+"), + linebelowheader=Line("+", "-", "+", "+"), + linebetweenrows=None, + linebelow=Line("+", "-", "+", "+"), + headerrow=DataRow("|", "|", "|"), + datarow=DataRow("|", "|", "|"), + padding=1, + with_header_hide=None, + ), + "psql": TableFormat( + lineabove=Line("+", "-", "+", "+"), + linebelowheader=Line("|", "-", "+", "|"), + linebetweenrows=None, + linebelow=Line("+", "-", "+", "+"), + headerrow=DataRow("|", "|", "|"), + datarow=DataRow("|", "|", "|"), + padding=1, + with_header_hide=None, + ), + "rst": TableFormat( + lineabove=Line("", "=", " ", ""), + linebelowheader=Line("", "=", " ", ""), + linebetweenrows=None, + linebelow=Line("", "=", " ", ""), + headerrow=DataRow("", " ", ""), + datarow=DataRow("", " ", ""), + padding=0, + with_header_hide=None, + ), + "mediawiki": TableFormat( + lineabove=Line( + '{| class="wikitable" style="text-align: left;"', + "", + "", + "\n|+ \n|-", + ), + linebelowheader=Line("|-", "", "", ""), + linebetweenrows=Line("|-", "", "", ""), + linebelow=Line("|}", "", "", ""), + headerrow=partial(_mediawiki_row_with_attrs, "!"), + datarow=partial(_mediawiki_row_with_attrs, "|"), + padding=0, + with_header_hide=None, + ), + "moinmoin": TableFormat( + lineabove=None, + linebelowheader=None, + linebetweenrows=None, + linebelow=None, + headerrow=partial(_moin_row_with_attrs, "||", header="'''"), + datarow=partial(_moin_row_with_attrs, "||"), + padding=1, + with_header_hide=None, + ), + "youtrack": TableFormat( + lineabove=None, + linebelowheader=None, + linebetweenrows=None, + linebelow=None, + headerrow=DataRow("|| ", " || ", " || "), + datarow=DataRow("| ", " | ", " |"), + padding=1, + with_header_hide=None, + ), + "html": TableFormat( + lineabove=_html_begin_table_without_header, + linebelowheader="", + linebetweenrows=None, + linebelow=Line("\n
", "", "", ""), + headerrow=partial(_html_row_with_attrs, "th", False), + datarow=partial(_html_row_with_attrs, "td", False), + padding=0, + with_header_hide=["lineabove"], + ), + "unsafehtml": TableFormat( + lineabove=_html_begin_table_without_header, + linebelowheader="", + linebetweenrows=None, + linebelow=Line("\n", "", "", ""), + headerrow=partial(_html_row_with_attrs, "th", True), + datarow=partial(_html_row_with_attrs, "td", True), + padding=0, + with_header_hide=["lineabove"], + ), + "latex": TableFormat( + lineabove=_latex_line_begin_tabular, + linebelowheader=Line("\\hline", "", "", ""), + linebetweenrows=None, + linebelow=Line("\\hline\n\\end{tabular}", "", "", ""), + headerrow=_latex_row, + datarow=_latex_row, + padding=1, + with_header_hide=None, + ), + "latex_raw": TableFormat( + lineabove=_latex_line_begin_tabular, + linebelowheader=Line("\\hline", "", "", ""), + linebetweenrows=None, + linebelow=Line("\\hline\n\\end{tabular}", "", "", ""), + headerrow=partial(_latex_row, escrules={}), + datarow=partial(_latex_row, escrules={}), + padding=1, + with_header_hide=None, + ), + "latex_booktabs": TableFormat( + lineabove=partial(_latex_line_begin_tabular, booktabs=True), + linebelowheader=Line("\\midrule", "", "", ""), + linebetweenrows=None, + linebelow=Line("\\bottomrule\n\\end{tabular}", "", "", ""), + headerrow=_latex_row, + datarow=_latex_row, + padding=1, + with_header_hide=None, + ), + "tsv": TableFormat( + lineabove=None, + linebelowheader=None, + linebetweenrows=None, + linebelow=None, + headerrow=DataRow("", "\t", ""), + datarow=DataRow("", "\t", ""), + padding=0, + with_header_hide=None, + ), + "textile": TableFormat( + lineabove=None, + linebelowheader=None, + linebetweenrows=None, + linebelow=None, + headerrow=DataRow("|_. ", "|_.", "|"), + datarow=_textile_row_with_attrs, + padding=1, + with_header_hide=None, + ), +} + + +tabulate_formats = list(sorted(_table_formats.keys())) + +# The table formats for which multiline cells will be folded into subsequent +# table rows. The key is the original format specified at the API. The value is +# the format that will be used to represent the original format. +multiline_formats = { + "plain": "plain", + "simple": "simple", + "grid": "grid", + "fancy_grid": "fancy_grid", + "pipe": "pipe", + "orgtbl": "orgtbl", + "jira": "jira", + "presto": "presto", + "pretty": "pretty", + "psql": "psql", + "rst": "rst", +} + +# TODO: Add multiline support for the remaining table formats: +# - mediawiki: Replace \n with
+# - moinmoin: TBD +# - youtrack: TBD +# - html: Replace \n with
+# - latex*: Use "makecell" package: In header, replace X\nY with +# \thead{X\\Y} and in data row, replace X\nY with \makecell{X\\Y} +# - tsv: TBD +# - textile: Replace \n with
(must be well-formed XML) + +_multiline_codes = re.compile(r"\r|\n|\r\n") +_multiline_codes_bytes = re.compile(b"\r|\n|\r\n") +_invisible_codes = re.compile( + r"\x1b\[\d+[;\d]*m|\x1b\[\d*\;\d*\;\d*m" +) # ANSI color codes +_invisible_codes_bytes = re.compile( + b"\x1b\\[\\d+\\[;\\d]*m|\x1b\\[\\d*;\\d*;\\d*m" +) # ANSI color codes + + +def simple_separated_format(separator): + """Construct a simple TableFormat with columns separated by a separator. + + >>> tsv = simple_separated_format("\\t") ; \ + tabulate([["foo", 1], ["spam", 23]], tablefmt=tsv) == 'foo \\t 1\\nspam\\t23' + True + + """ + return TableFormat( + None, + None, + None, + None, + headerrow=DataRow("", separator, ""), + datarow=DataRow("", separator, ""), + padding=0, + with_header_hide=None, + ) + + +def _isconvertible(conv, string): + try: + conv(string) + return True + except (ValueError, TypeError): + return False + + +def _isnumber(string): + """ + >>> _isnumber("123.45") + True + >>> _isnumber("123") + True + >>> _isnumber("spam") + False + >>> _isnumber("123e45678") + False + >>> _isnumber("inf") + True + """ + if not _isconvertible(float, string): + return False + elif isinstance(string, (_text_type, _binary_type)) and ( + math.isinf(float(string)) or math.isnan(float(string)) + ): + return string.lower() in ["inf", "-inf", "nan"] + return True + + +def _isint(string, inttype=int): + """ + >>> _isint("123") + True + >>> _isint("123.45") + False + """ + return ( + type(string) is inttype + or (isinstance(string, _binary_type) or isinstance(string, _text_type)) + and _isconvertible(inttype, string) + ) + + +def _isbool(string): + """ + >>> _isbool(True) + True + >>> _isbool("False") + True + >>> _isbool(1) + False + """ + return type(string) is _bool_type or ( + isinstance(string, (_binary_type, _text_type)) and string in ("True", "False") + ) + + +def _type(string, has_invisible=True, numparse=True): + """The least generic type (type(None), int, float, str, unicode). + + >>> _type(None) is type(None) + True + >>> _type("foo") is type("") + True + >>> _type("1") is type(1) + True + >>> _type('\x1b[31m42\x1b[0m') is type(42) + True + >>> _type('\x1b[31m42\x1b[0m') is type(42) + True + + """ + + if has_invisible and ( + isinstance(string, _text_type) or isinstance(string, _binary_type) + ): + string = _strip_invisible(string) + + if string is None: + return _none_type + elif hasattr(string, "isoformat"): # datetime.datetime, date, and time + return _text_type + elif _isbool(string): + return _bool_type + elif _isint(string) and numparse: + return int + elif _isint(string, _long_type) and numparse: + return int + elif _isnumber(string) and numparse: + return float + elif isinstance(string, _binary_type): + return _binary_type + else: + return _text_type + + +def _afterpoint(string): + """Symbols after a decimal point, -1 if the string lacks the decimal point. + + >>> _afterpoint("123.45") + 2 + >>> _afterpoint("1001") + -1 + >>> _afterpoint("eggs") + -1 + >>> _afterpoint("123e45") + 2 + + """ + if _isnumber(string): + if _isint(string): + return -1 + else: + pos = string.rfind(".") + pos = string.lower().rfind("e") if pos < 0 else pos + if pos >= 0: + return len(string) - pos - 1 + else: + return -1 # no point + else: + return -1 # not a number + + +def _padleft(width, s): + """Flush right. + + >>> _padleft(6, '\u044f\u0439\u0446\u0430') == ' \u044f\u0439\u0446\u0430' + True + + """ + fmt = "{0:>%ds}" % width + return fmt.format(s) + + +def _padright(width, s): + """Flush left. + + >>> _padright(6, '\u044f\u0439\u0446\u0430') == '\u044f\u0439\u0446\u0430 ' + True + + """ + fmt = "{0:<%ds}" % width + return fmt.format(s) + + +def _padboth(width, s): + """Center string. + + >>> _padboth(6, '\u044f\u0439\u0446\u0430') == ' \u044f\u0439\u0446\u0430 ' + True + + """ + fmt = "{0:^%ds}" % width + return fmt.format(s) + + +def _padnone(ignore_width, s): + return s + + +def _strip_invisible(s): + "Remove invisible ANSI color codes." + if isinstance(s, _text_type): + return re.sub(_invisible_codes, "", s) + else: # a bytestring + return re.sub(_invisible_codes_bytes, "", s) + + +def _visible_width(s): + """Visible width of a printed string. ANSI color codes are removed. + + >>> _visible_width('\x1b[31mhello\x1b[0m'), _visible_width("world") + (5, 5) + + """ + # optional wide-character support + if wcwidth is not None and WIDE_CHARS_MODE: + len_fn = wcwidth.wcswidth + else: + len_fn = len + if isinstance(s, _text_type) or isinstance(s, _binary_type): + return len_fn(_strip_invisible(s)) + else: + return len_fn(_text_type(s)) + + +def _is_multiline(s): + if isinstance(s, _text_type): + return bool(re.search(_multiline_codes, s)) + else: # a bytestring + return bool(re.search(_multiline_codes_bytes, s)) + + +def _multiline_width(multiline_s, line_width_fn=len): + """Visible width of a potentially multiline content.""" + return max(map(line_width_fn, re.split("[\r\n]", multiline_s))) + + +def _choose_width_fn(has_invisible, enable_widechars, is_multiline): + """Return a function to calculate visible cell width.""" + if has_invisible: + line_width_fn = _visible_width + elif enable_widechars: # optional wide-character support if available + line_width_fn = wcwidth.wcswidth + else: + line_width_fn = len + if is_multiline: + width_fn = lambda s: _multiline_width(s, line_width_fn) # noqa + else: + width_fn = line_width_fn + return width_fn + + +def _align_column_choose_padfn(strings, alignment, has_invisible): + if alignment == "right": + if not PRESERVE_WHITESPACE: + strings = [s.strip() for s in strings] + padfn = _padleft + elif alignment == "center": + if not PRESERVE_WHITESPACE: + strings = [s.strip() for s in strings] + padfn = _padboth + elif alignment == "decimal": + if has_invisible: + decimals = [_afterpoint(_strip_invisible(s)) for s in strings] + else: + decimals = [_afterpoint(s) for s in strings] + maxdecimals = max(decimals) + strings = [s + (maxdecimals - decs) * " " for s, decs in zip(strings, decimals)] + padfn = _padleft + elif not alignment: + padfn = _padnone + else: + if not PRESERVE_WHITESPACE: + strings = [s.strip() for s in strings] + padfn = _padright + return strings, padfn + + +def _align_column( + strings, + alignment, + minwidth=0, + has_invisible=True, + enable_widechars=False, + is_multiline=False, +): + """[string] -> [padded_string]""" + strings, padfn = _align_column_choose_padfn(strings, alignment, has_invisible) + width_fn = _choose_width_fn(has_invisible, enable_widechars, is_multiline) + + s_widths = list(map(width_fn, strings)) + maxwidth = max(max(s_widths), minwidth) + # TODO: refactor column alignment in single-line and multiline modes + if is_multiline: + if not enable_widechars and not has_invisible: + padded_strings = [ + "\n".join([padfn(maxwidth, s) for s in ms.splitlines()]) + for ms in strings + ] + else: + # enable wide-character width corrections + s_lens = [max((len(s) for s in re.split("[\r\n]", ms))) for ms in strings] + visible_widths = [maxwidth - (w - l) for w, l in zip(s_widths, s_lens)] + # wcswidth and _visible_width don't count invisible characters; + # padfn doesn't need to apply another correction + padded_strings = [ + "\n".join([padfn(w, s) for s in (ms.splitlines() or ms)]) + for ms, w in zip(strings, visible_widths) + ] + else: # single-line cell values + if not enable_widechars and not has_invisible: + padded_strings = [padfn(maxwidth, s) for s in strings] + else: + # enable wide-character width corrections + s_lens = list(map(len, strings)) + visible_widths = [maxwidth - (w - l) for w, l in zip(s_widths, s_lens)] + # wcswidth and _visible_width don't count invisible characters; + # padfn doesn't need to apply another correction + padded_strings = [padfn(w, s) for s, w in zip(strings, visible_widths)] + return padded_strings + + +def _more_generic(type1, type2): + types = { + _none_type: 0, + _bool_type: 1, + int: 2, + float: 3, + _binary_type: 4, + _text_type: 5, + } + invtypes = { + 5: _text_type, + 4: _binary_type, + 3: float, + 2: int, + 1: _bool_type, + 0: _none_type, + } + moregeneric = max(types.get(type1, 5), types.get(type2, 5)) + return invtypes[moregeneric] + + +def _column_type(strings, has_invisible=True, numparse=True): + """The least generic type all column values are convertible to. + + >>> _column_type([True, False]) is _bool_type + True + >>> _column_type(["1", "2"]) is _int_type + True + >>> _column_type(["1", "2.3"]) is _float_type + True + >>> _column_type(["1", "2.3", "four"]) is _text_type + True + >>> _column_type(["four", '\u043f\u044f\u0442\u044c']) is _text_type + True + >>> _column_type([None, "brux"]) is _text_type + True + >>> _column_type([1, 2, None]) is _int_type + True + >>> import datetime as dt + >>> _column_type([dt.datetime(1991,2,19), dt.time(17,35)]) is _text_type + True + + """ + types = [_type(s, has_invisible, numparse) for s in strings] + return reduce(_more_generic, types, _bool_type) + + +def _format(val, valtype, floatfmt, missingval="", has_invisible=True): + """Format a value accoding to its type. + + Unicode is supported: + + >>> hrow = ['\u0431\u0443\u043a\u0432\u0430', '\u0446\u0438\u0444\u0440\u0430'] ; \ + tbl = [['\u0430\u0437', 2], ['\u0431\u0443\u043a\u0438', 4]] ; \ + good_result = '\\u0431\\u0443\\u043a\\u0432\\u0430 \\u0446\\u0438\\u0444\\u0440\\u0430\\n------- -------\\n\\u0430\\u0437 2\\n\\u0431\\u0443\\u043a\\u0438 4' ; \ + tabulate(tbl, headers=hrow) == good_result + True + + """ # noqa + if val is None: + return missingval + + if valtype in [int, _text_type]: + return "{0}".format(val) + elif valtype is _binary_type: + try: + return _text_type(val, "ascii") + except TypeError: + return _text_type(val) + elif valtype is float: + is_a_colored_number = has_invisible and isinstance( + val, (_text_type, _binary_type) + ) + if is_a_colored_number: + raw_val = _strip_invisible(val) + formatted_val = format(float(raw_val), floatfmt) + return val.replace(raw_val, formatted_val) + else: + return format(float(val), floatfmt) + else: + return "{0}".format(val) + + +def _align_header( + header, alignment, width, visible_width, is_multiline=False, width_fn=None +): + "Pad string header to width chars given known visible_width of the header." + if is_multiline: + header_lines = re.split(_multiline_codes, header) + padded_lines = [ + _align_header(h, alignment, width, width_fn(h)) for h in header_lines + ] + return "\n".join(padded_lines) + # else: not multiline + ninvisible = len(header) - visible_width + width += ninvisible + if alignment == "left": + return _padright(width, header) + elif alignment == "center": + return _padboth(width, header) + elif not alignment: + return "{0}".format(header) + else: + return _padleft(width, header) + + +def _prepend_row_index(rows, index): + """Add a left-most index column.""" + if index is None or index is False: + return rows + if len(index) != len(rows): + print("index=", index) + print("rows=", rows) + raise ValueError("index must be as long as the number of data rows") + rows = [[v] + list(row) for v, row in zip(index, rows)] + return rows + + +def _bool(val): + "A wrapper around standard bool() which doesn't throw on NumPy arrays" + try: + return bool(val) + except ValueError: # val is likely to be a numpy array with many elements + return False + + +def _normalize_tabular_data(tabular_data, headers, showindex="default"): + """Transform a supported data type to a list of lists, and a list of headers. + + Supported tabular data types: + + * list-of-lists or another iterable of iterables + + * list of named tuples (usually used with headers="keys") + + * list of dicts (usually used with headers="keys") + + * list of OrderedDicts (usually used with headers="keys") + + * 2D NumPy arrays + + * NumPy record arrays (usually used with headers="keys") + + * dict of iterables (usually used with headers="keys") + + * pandas.DataFrame (usually used with headers="keys") + + The first row can be used as headers if headers="firstrow", + column indices can be used as headers if headers="keys". + + If showindex="default", show row indices of the pandas.DataFrame. + If showindex="always", show row indices for all types of data. + If showindex="never", don't show row indices for all types of data. + If showindex is an iterable, show its values as row indices. + + """ + + try: + bool(headers) + is_headers2bool_broken = False # noqa + except ValueError: # numpy.ndarray, pandas.core.index.Index, ... + is_headers2bool_broken = True # noqa + headers = list(headers) + + index = None + if hasattr(tabular_data, "keys") and hasattr(tabular_data, "values"): + # dict-like and pandas.DataFrame? + if hasattr(tabular_data.values, "__call__"): + # likely a conventional dict + keys = tabular_data.keys() + rows = list( + izip_longest(*tabular_data.values()) + ) # columns have to be transposed + elif hasattr(tabular_data, "index"): + # values is a property, has .index => it's likely a pandas.DataFrame (pandas 0.11.0) + keys = list(tabular_data) + if tabular_data.index.name is not None: + if isinstance(tabular_data.index.name, list): + keys[:0] = tabular_data.index.name + else: + keys[:0] = [tabular_data.index.name] + vals = tabular_data.values # values matrix doesn't need to be transposed + # for DataFrames add an index per default + index = list(tabular_data.index) + rows = [list(row) for row in vals] + else: + raise ValueError("tabular data doesn't appear to be a dict or a DataFrame") + + if headers == "keys": + headers = list(map(_text_type, keys)) # headers should be strings + + else: # it's a usual an iterable of iterables, or a NumPy array + rows = list(tabular_data) + + if headers == "keys" and not rows: + # an empty table (issue #81) + headers = [] + elif ( + headers == "keys" + and hasattr(tabular_data, "dtype") + and getattr(tabular_data.dtype, "names") + ): + # numpy record array + headers = tabular_data.dtype.names + elif ( + headers == "keys" + and len(rows) > 0 + and isinstance(rows[0], tuple) + and hasattr(rows[0], "_fields") + ): + # namedtuple + headers = list(map(_text_type, rows[0]._fields)) + elif len(rows) > 0 and isinstance(rows[0], dict): + # dict or OrderedDict + uniq_keys = set() # implements hashed lookup + keys = [] # storage for set + if headers == "firstrow": + firstdict = rows[0] if len(rows) > 0 else {} + keys.extend(firstdict.keys()) + uniq_keys.update(keys) + rows = rows[1:] + for row in rows: + for k in row.keys(): + # Save unique items in input order + if k not in uniq_keys: + keys.append(k) + uniq_keys.add(k) + if headers == "keys": + headers = keys + elif isinstance(headers, dict): + # a dict of headers for a list of dicts + headers = [headers.get(k, k) for k in keys] + headers = list(map(_text_type, headers)) + elif headers == "firstrow": + if len(rows) > 0: + headers = [firstdict.get(k, k) for k in keys] + headers = list(map(_text_type, headers)) + else: + headers = [] + elif headers: + raise ValueError( + "headers for a list of dicts is not a dict or a keyword" + ) + rows = [[row.get(k) for k in keys] for row in rows] + + elif ( + headers == "keys" + and hasattr(tabular_data, "description") + and hasattr(tabular_data, "fetchone") + and hasattr(tabular_data, "rowcount") + ): + # Python Database API cursor object (PEP 0249) + # print tabulate(cursor, headers='keys') + headers = [column[0] for column in tabular_data.description] + + elif headers == "keys" and len(rows) > 0: + # keys are column indices + headers = list(map(_text_type, range(len(rows[0])))) + + # take headers from the first row if necessary + if headers == "firstrow" and len(rows) > 0: + if index is not None: + headers = [index[0]] + list(rows[0]) + index = index[1:] + else: + headers = rows[0] + headers = list(map(_text_type, headers)) # headers should be strings + rows = rows[1:] + + headers = list(map(_text_type, headers)) + rows = list(map(list, rows)) + + # add or remove an index column + showindex_is_a_str = type(showindex) in [_text_type, _binary_type] + if showindex == "default" and index is not None: + rows = _prepend_row_index(rows, index) + elif isinstance(showindex, Iterable) and not showindex_is_a_str: + rows = _prepend_row_index(rows, list(showindex)) + elif showindex == "always" or (_bool(showindex) and not showindex_is_a_str): + if index is None: + index = list(range(len(rows))) + rows = _prepend_row_index(rows, index) + elif showindex == "never" or (not _bool(showindex) and not showindex_is_a_str): + pass + + # pad with empty headers for initial columns if necessary + if headers and len(rows) > 0: + nhs = len(headers) + ncols = len(rows[0]) + if nhs < ncols: + headers = [""] * (ncols - nhs) + headers + + return rows, headers + + +def tabulate( + tabular_data, + headers=(), + tablefmt="simple", + floatfmt=_DEFAULT_FLOATFMT, + numalign="decimal", + stralign="left", + missingval=_DEFAULT_MISSINGVAL, + showindex="default", + disable_numparse=False, + colalign=None, +): + """Format a fixed width table for pretty printing. + + >>> print(tabulate([[1, 2.34], [-56, "8.999"], ["2", "10001"]])) + --- --------- + 1 2.34 + -56 8.999 + 2 10001 + --- --------- + + The first required argument (`tabular_data`) can be a + list-of-lists (or another iterable of iterables), a list of named + tuples, a dictionary of iterables, an iterable of dictionaries, + a two-dimensional NumPy array, NumPy record array, or a Pandas' + dataframe. + + + Table headers + ------------- + + To print nice column headers, supply the second argument (`headers`): + + - `headers` can be an explicit list of column headers + - if `headers="firstrow"`, then the first row of data is used + - if `headers="keys"`, then dictionary keys or column indices are used + + Otherwise a headerless table is produced. + + If the number of headers is less than the number of columns, they + are supposed to be names of the last columns. This is consistent + with the plain-text format of R and Pandas' dataframes. + + >>> print(tabulate([["sex","age"],["Alice","F",24],["Bob","M",19]], + ... headers="firstrow")) + sex age + ----- ----- ----- + Alice F 24 + Bob M 19 + + By default, pandas.DataFrame data have an additional column called + row index. To add a similar column to all other types of data, + use `showindex="always"` or `showindex=True`. To suppress row indices + for all types of data, pass `showindex="never" or `showindex=False`. + To add a custom row index column, pass `showindex=some_iterable`. + + >>> print(tabulate([["F",24],["M",19]], showindex="always")) + - - -- + 0 F 24 + 1 M 19 + - - -- + + + Column alignment + ---------------- + + `tabulate` tries to detect column types automatically, and aligns + the values properly. By default it aligns decimal points of the + numbers (or flushes integer numbers to the right), and flushes + everything else to the left. Possible column alignments + (`numalign`, `stralign`) are: "right", "center", "left", "decimal" + (only for `numalign`), and None (to disable alignment). + + + Table formats + ------------- + + `floatfmt` is a format specification used for columns which + contain numeric data with a decimal point. This can also be + a list or tuple of format strings, one per column. + + `None` values are replaced with a `missingval` string (like + `floatfmt`, this can also be a list of values for different + columns): + + >>> print(tabulate([["spam", 1, None], + ... ["eggs", 42, 3.14], + ... ["other", None, 2.7]], missingval="?")) + ----- -- ---- + spam 1 ? + eggs 42 3.14 + other ? 2.7 + ----- -- ---- + + Various plain-text table formats (`tablefmt`) are supported: + 'plain', 'simple', 'grid', 'pipe', 'orgtbl', 'rst', 'mediawiki', + 'latex', 'latex_raw' and 'latex_booktabs'. Variable `tabulate_formats` + contains the list of currently supported formats. + + "plain" format doesn't use any pseudographics to draw tables, + it separates columns with a double space: + + >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], + ... ["strings", "numbers"], "plain")) + strings numbers + spam 41.9999 + eggs 451 + + >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="plain")) + spam 41.9999 + eggs 451 + + "simple" format is like Pandoc simple_tables: + + >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], + ... ["strings", "numbers"], "simple")) + strings numbers + --------- --------- + spam 41.9999 + eggs 451 + + >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="simple")) + ---- -------- + spam 41.9999 + eggs 451 + ---- -------- + + "grid" is similar to tables produced by Emacs table.el package or + Pandoc grid_tables: + + >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], + ... ["strings", "numbers"], "grid")) + +-----------+-----------+ + | strings | numbers | + +===========+===========+ + | spam | 41.9999 | + +-----------+-----------+ + | eggs | 451 | + +-----------+-----------+ + + >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="grid")) + +------+----------+ + | spam | 41.9999 | + +------+----------+ + | eggs | 451 | + +------+----------+ + + "fancy_grid" draws a grid using box-drawing characters: + + >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], + ... ["strings", "numbers"], "fancy_grid")) + ╒═══════════╤═══════════╕ + │ strings │ numbers │ + ╞═══════════╪═══════════╡ + │ spam │ 41.9999 │ + ├───────────┼───────────┤ + │ eggs │ 451 │ + ╘═══════════╧═══════════╛ + + "pipe" is like tables in PHP Markdown Extra extension or Pandoc + pipe_tables: + + >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], + ... ["strings", "numbers"], "pipe")) + | strings | numbers | + |:----------|----------:| + | spam | 41.9999 | + | eggs | 451 | + + "presto" is like tables produce by the Presto CLI: + + >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], + ... ["strings", "numbers"], "presto")) + strings | numbers + -----------+----------- + spam | 41.9999 + eggs | 451 + + >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="pipe")) + |:-----|---------:| + | spam | 41.9999 | + | eggs | 451 | + + "orgtbl" is like tables in Emacs org-mode and orgtbl-mode. They + are slightly different from "pipe" format by not using colons to + define column alignment, and using a "+" sign to indicate line + intersections: + + >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], + ... ["strings", "numbers"], "orgtbl")) + | strings | numbers | + |-----------+-----------| + | spam | 41.9999 | + | eggs | 451 | + + + >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="orgtbl")) + | spam | 41.9999 | + | eggs | 451 | + + "rst" is like a simple table format from reStructuredText; please + note that reStructuredText accepts also "grid" tables: + + >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], + ... ["strings", "numbers"], "rst")) + ========= ========= + strings numbers + ========= ========= + spam 41.9999 + eggs 451 + ========= ========= + + >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="rst")) + ==== ======== + spam 41.9999 + eggs 451 + ==== ======== + + "mediawiki" produces a table markup used in Wikipedia and on other + MediaWiki-based sites: + + >>> print(tabulate([["strings", "numbers"], ["spam", 41.9999], ["eggs", "451.0"]], + ... headers="firstrow", tablefmt="mediawiki")) + {| class="wikitable" style="text-align: left;" + |+ + |- + ! strings !! align="right"| numbers + |- + | spam || align="right"| 41.9999 + |- + | eggs || align="right"| 451 + |} + + "html" produces HTML markup as an html.escape'd str + with a ._repr_html_ method so that Jupyter Lab and Notebook display the HTML + and a .str property so that the raw HTML remains accessible + the unsafehtml table format can be used if an unescaped HTML format is required: + + >>> print(tabulate([["strings", "numbers"], ["spam", 41.9999], ["eggs", "451.0"]], + ... headers="firstrow", tablefmt="html")) + + + + + + + + +
strings numbers
spam 41.9999
eggs 451
+ + "latex" produces a tabular environment of LaTeX document markup: + + >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="latex")) + \\begin{tabular}{lr} + \\hline + spam & 41.9999 \\\\ + eggs & 451 \\\\ + \\hline + \\end{tabular} + + "latex_raw" is similar to "latex", but doesn't escape special characters, + such as backslash and underscore, so LaTeX commands may embedded into + cells' values: + + >>> print(tabulate([["spam$_9$", 41.9999], ["\\\\emph{eggs}", "451.0"]], tablefmt="latex_raw")) + \\begin{tabular}{lr} + \\hline + spam$_9$ & 41.9999 \\\\ + \\emph{eggs} & 451 \\\\ + \\hline + \\end{tabular} + + "latex_booktabs" produces a tabular environment of LaTeX document markup + using the booktabs.sty package: + + >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="latex_booktabs")) + \\begin{tabular}{lr} + \\toprule + spam & 41.9999 \\\\ + eggs & 451 \\\\ + \\bottomrule + \\end{tabular} + + Number parsing + -------------- + By default, anything which can be parsed as a number is a number. + This ensures numbers represented as strings are aligned properly. + This can lead to weird results for particular strings such as + specific git SHAs e.g. "42992e1" will be parsed into the number + 429920 and aligned as such. + + To completely disable number parsing (and alignment), use + `disable_numparse=True`. For more fine grained control, a list column + indices is used to disable number parsing only on those columns + e.g. `disable_numparse=[0, 2]` would disable number parsing only on the + first and third columns. + """ + + if tabular_data is None: + tabular_data = [] + list_of_lists, headers = _normalize_tabular_data( + tabular_data, headers, showindex=showindex + ) + + # empty values in the first column of RST tables should be escaped (issue #82) + # "" should be escaped as "\\ " or ".." + if tablefmt == "rst": + list_of_lists, headers = _rst_escape_first_column(list_of_lists, headers) + + # PrettyTable formatting does not use any extra padding. + # Numbers are not parsed and are treated the same as strings for alignment. + # Check if pretty is the format being used and override the defaults so it + # does not impact other formats. + min_padding = MIN_PADDING + if tablefmt == "pretty": + min_padding = 0 + disable_numparse = True + numalign = "center" + stralign = "center" + + # optimization: look for ANSI control codes once, + # enable smart width functions only if a control code is found + plain_text = "\t".join( + ["\t".join(map(_text_type, headers))] + + ["\t".join(map(_text_type, row)) for row in list_of_lists] + ) + + has_invisible = re.search(_invisible_codes, plain_text) + enable_widechars = wcwidth is not None and WIDE_CHARS_MODE + if ( + not isinstance(tablefmt, TableFormat) + and tablefmt in multiline_formats + and _is_multiline(plain_text) + ): + tablefmt = multiline_formats.get(tablefmt, tablefmt) + is_multiline = True + else: + is_multiline = False + width_fn = _choose_width_fn(has_invisible, enable_widechars, is_multiline) + + # format rows and columns, convert numeric values to strings + cols = list(izip_longest(*list_of_lists)) + numparses = _expand_numparse(disable_numparse, len(cols)) + coltypes = [_column_type(col, numparse=np) for col, np in zip(cols, numparses)] + if isinstance(floatfmt, basestring): # old version + float_formats = len(cols) * [ + floatfmt + ] # just duplicate the string to use in each column + else: # if floatfmt is list, tuple etc we have one per column + float_formats = list(floatfmt) + if len(float_formats) < len(cols): + float_formats.extend((len(cols) - len(float_formats)) * [_DEFAULT_FLOATFMT]) + if isinstance(missingval, basestring): + missing_vals = len(cols) * [missingval] + else: + missing_vals = list(missingval) + if len(missing_vals) < len(cols): + missing_vals.extend((len(cols) - len(missing_vals)) * [_DEFAULT_MISSINGVAL]) + cols = [ + [_format(v, ct, fl_fmt, miss_v, has_invisible) for v in c] + for c, ct, fl_fmt, miss_v in zip(cols, coltypes, float_formats, missing_vals) + ] + + # align columns + aligns = [numalign if ct in [int, float] else stralign for ct in coltypes] + if colalign is not None: + assert isinstance(colalign, Iterable) + for idx, align in enumerate(colalign): + aligns[idx] = align + minwidths = ( + [width_fn(h) + min_padding for h in headers] if headers else [0] * len(cols) + ) + cols = [ + _align_column(c, a, minw, has_invisible, enable_widechars, is_multiline) + for c, a, minw in zip(cols, aligns, minwidths) + ] + + if headers: + # align headers and add headers + t_cols = cols or [[""]] * len(headers) + t_aligns = aligns or [stralign] * len(headers) + minwidths = [ + max(minw, max(width_fn(cl) for cl in c)) + for minw, c in zip(minwidths, t_cols) + ] + headers = [ + _align_header(h, a, minw, width_fn(h), is_multiline, width_fn) + for h, a, minw in zip(headers, t_aligns, minwidths) + ] + rows = list(zip(*cols)) + else: + minwidths = [max(width_fn(cl) for cl in c) for c in cols] + rows = list(zip(*cols)) + + if not isinstance(tablefmt, TableFormat): + tablefmt = _table_formats.get(tablefmt, _table_formats["simple"]) + + return _format_table(tablefmt, headers, rows, minwidths, aligns, is_multiline) + + +def _expand_numparse(disable_numparse, column_count): + """ + Return a list of bools of length `column_count` which indicates whether + number parsing should be used on each column. + If `disable_numparse` is a list of indices, each of those indices are False, + and everything else is True. + If `disable_numparse` is a bool, then the returned list is all the same. + """ + if isinstance(disable_numparse, Iterable): + numparses = [True] * column_count + for index in disable_numparse: + numparses[index] = False + return numparses + else: + return [not disable_numparse] * column_count + + +def _pad_row(cells, padding): + if cells: + pad = " " * padding + padded_cells = [pad + cell + pad for cell in cells] + return padded_cells + else: + return cells + + +def _build_simple_row(padded_cells, rowfmt): + "Format row according to DataRow format without padding." + begin, sep, end = rowfmt + return (begin + sep.join(padded_cells) + end).rstrip() + + +def _build_row(padded_cells, colwidths, colaligns, rowfmt): + "Return a string which represents a row of data cells." + if not rowfmt: + return None + if hasattr(rowfmt, "__call__"): + return rowfmt(padded_cells, colwidths, colaligns) + else: + return _build_simple_row(padded_cells, rowfmt) + + +def _append_basic_row(lines, padded_cells, colwidths, colaligns, rowfmt): + lines.append(_build_row(padded_cells, colwidths, colaligns, rowfmt)) + return lines + + +def _append_multiline_row( + lines, padded_multiline_cells, padded_widths, colaligns, rowfmt, pad +): + colwidths = [w - 2 * pad for w in padded_widths] + cells_lines = [c.splitlines() for c in padded_multiline_cells] + nlines = max(map(len, cells_lines)) # number of lines in the row + # vertically pad cells where some lines are missing + cells_lines = [ + (cl + [" " * w] * (nlines - len(cl))) for cl, w in zip(cells_lines, colwidths) + ] + lines_cells = [[cl[i] for cl in cells_lines] for i in range(nlines)] + for ln in lines_cells: + padded_ln = _pad_row(ln, pad) + _append_basic_row(lines, padded_ln, colwidths, colaligns, rowfmt) + return lines + + +def _build_line(colwidths, colaligns, linefmt): + "Return a string which represents a horizontal line." + if not linefmt: + return None + if hasattr(linefmt, "__call__"): + return linefmt(colwidths, colaligns) + else: + begin, fill, sep, end = linefmt + cells = [fill * w for w in colwidths] + return _build_simple_row(cells, (begin, sep, end)) + + +def _append_line(lines, colwidths, colaligns, linefmt): + lines.append(_build_line(colwidths, colaligns, linefmt)) + return lines + + +class JupyterHTMLStr(str): + """Wrap the string with a _repr_html_ method so that Jupyter + displays the HTML table""" + + def _repr_html_(self): + return self + + @property + def str(self): + """add a .str property so that the raw string is still accessible""" + return self + + +def _format_table(fmt, headers, rows, colwidths, colaligns, is_multiline): + """Produce a plain-text representation of the table.""" + lines = [] + hidden = fmt.with_header_hide if (headers and fmt.with_header_hide) else [] + pad = fmt.padding + headerrow = fmt.headerrow + + padded_widths = [(w + 2 * pad) for w in colwidths] + if is_multiline: + pad_row = lambda row, _: row # noqa do it later, in _append_multiline_row + append_row = partial(_append_multiline_row, pad=pad) + else: + pad_row = _pad_row + append_row = _append_basic_row + + padded_headers = pad_row(headers, pad) + padded_rows = [pad_row(row, pad) for row in rows] + + if fmt.lineabove and "lineabove" not in hidden: + _append_line(lines, padded_widths, colaligns, fmt.lineabove) + + if padded_headers: + append_row(lines, padded_headers, padded_widths, colaligns, headerrow) + if fmt.linebelowheader and "linebelowheader" not in hidden: + _append_line(lines, padded_widths, colaligns, fmt.linebelowheader) + + if padded_rows and fmt.linebetweenrows and "linebetweenrows" not in hidden: + # initial rows with a line below + for row in padded_rows[:-1]: + append_row(lines, row, padded_widths, colaligns, fmt.datarow) + _append_line(lines, padded_widths, colaligns, fmt.linebetweenrows) + # the last row without a line below + append_row(lines, padded_rows[-1], padded_widths, colaligns, fmt.datarow) + else: + for row in padded_rows: + append_row(lines, row, padded_widths, colaligns, fmt.datarow) + + if fmt.linebelow and "linebelow" not in hidden: + _append_line(lines, padded_widths, colaligns, fmt.linebelow) + + if headers or rows: + output = "\n".join(lines) + if fmt.lineabove == _html_begin_table_without_header: + return JupyterHTMLStr(output) + else: + return output + else: # a completely empty table + return "" + + +def _main(): + """\ + Usage: tabulate [options] [FILE ...] + + Pretty-print tabular data. + See also https://github.com/astanin/python-tabulate + + FILE a filename of the file with tabular data; + if "-" or missing, read data from stdin. + + Options: + + -h, --help show this message + -1, --header use the first row of data as a table header + -o FILE, --output FILE print table to FILE (default: stdout) + -s REGEXP, --sep REGEXP use a custom column separator (default: whitespace) + -F FPFMT, --float FPFMT floating point number format (default: g) + -f FMT, --format FMT set output table format; supported formats: + plain, simple, grid, fancy_grid, pipe, orgtbl, + rst, mediawiki, html, latex, latex_raw, + latex_booktabs, tsv + (default: simple) + """ + import getopt + import sys + import textwrap + + usage = textwrap.dedent(_main.__doc__) + try: + opts, args = getopt.getopt( + sys.argv[1:], + "h1o:s:F:A:f:", + ["help", "header", "output", "sep=", "float=", "align=", "format="], + ) + except getopt.GetoptError as e: + print(e) + print(usage) + sys.exit(2) + headers = [] + floatfmt = _DEFAULT_FLOATFMT + colalign = None + tablefmt = "simple" + sep = r"\s+" + outfile = "-" + for opt, value in opts: + if opt in ["-1", "--header"]: + headers = "firstrow" + elif opt in ["-o", "--output"]: + outfile = value + elif opt in ["-F", "--float"]: + floatfmt = value + elif opt in ["-C", "--colalign"]: + colalign = value.split() + elif opt in ["-f", "--format"]: + if value not in tabulate_formats: + print("%s is not a supported table format" % value) + print(usage) + sys.exit(3) + tablefmt = value + elif opt in ["-s", "--sep"]: + sep = value + elif opt in ["-h", "--help"]: + print(usage) + sys.exit(0) + files = [sys.stdin] if not args else args + with (sys.stdout if outfile == "-" else open(outfile, "w")) as out: + for f in files: + if f == "-": + f = sys.stdin + if _is_file(f): + _pprint_file( + f, + headers=headers, + tablefmt=tablefmt, + sep=sep, + floatfmt=floatfmt, + file=out, + colalign=colalign, + ) + else: + with open(f) as fobj: + _pprint_file( + fobj, + headers=headers, + tablefmt=tablefmt, + sep=sep, + floatfmt=floatfmt, + file=out, + colalign=colalign, + ) + + +def _pprint_file(fobject, headers, tablefmt, sep, floatfmt, file, colalign): + rows = fobject.readlines() + table = [re.split(sep, r.rstrip()) for r in rows if r.strip()] + print( + tabulate(table, headers, tablefmt, floatfmt=floatfmt, colalign=colalign), + file=file, + ) + + +if __name__ == "__main__": + _main() diff --git a/jans-cli-tui/cli/swagger_yaml.json b/jans-cli-tui/cli/swagger_yaml.json new file mode 100644 index 00000000000..a028a7b051c --- /dev/null +++ b/jans-cli-tui/cli/swagger_yaml.json @@ -0,0 +1,10406 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "jans-config-api", + "description": "jans-config-api - Authorization services", + "contact": { + "email": "xxx@gluu.org" + }, + "license": { + "name": "License", + "url": "https://github.com/JanssenProject/blob/master/LICENSE" + }, + "version": "1.0.0" + }, + "servers": [ + { + "url": "https://jans.io/" + } + ], + "tags": [ + { + "name": "developers", + "description": "jans-config-api enables access to the features available via the existing Jans Authorization Server API." + }, + { + "name": "Attribute" + }, + { + "name": "Default Authentication Method" + }, + { + "name": "Cache Configuration" + }, + { + "name": "Cache Configuration \u2013 Memcached" + }, + { + "name": "Cache Configuration \u2013 Redis" + }, + { + "name": "Cache Configuration \u2013 in-Memory" + }, + { + "name": "Cache Configuration \u2013 Native-Persistence" + }, + { + "name": "Configuration \u2013 Properties" + }, + { + "name": "Fido2 - Configuration" + }, + { + "name": "Configuration \u2013 SMTP" + }, + { + "name": "Configuration \u2013 Logging" + }, + { + "name": "Configuration \u2013 JWK - JSON Web Key (JWK)" + }, + { + "name": "Custom Scripts" + }, + { + "name": "Database - LDAP configuration" + }, + { + "name": "Database - Couchbase configuration" + }, + { + "name": "OAuth - OpenID Connect - Clients" + }, + { + "name": "OAuth - UMA Resources" + }, + { + "name": "OAuth - Scopes" + }, + { + "name": "Statistics - User" + }, + { + "name": "Health - Check" + }, + { + "name": "Server Stats" + }, + { + "name": "Configuration \u2013 User Management" + }, + { + "name": "SCIM - Config Management" + }, + { + "name": "Organization Configuration" + }, + { + "name": "Auth Server Health - Check" + }, + { + "name": "Admin UI - Role" + }, + { + "name": "Admin UI - Permission" + }, + { + "name": "Admin UI - Role-Permissions Mapping" + }, + { + "name": "Admin UI - License" + } + ], + "paths": { + "/jans-config-api/api/v1/jans-auth-server/config": { + "get": { + "summary": "Gets all Jans authorization server configuration properties.", + "description": "Gets all Jans authorization server configuration properties.", + "operationId": "get-properties", + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/jans-auth-server/config/properties.readonly" + ] + } + ], + "tags": [ + "Configuration \u2013 Properties" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "AppConfiguration", + "description": "Jans Authorization Server config properties.", + "$ref": "#/components/schemas/AppConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + }, + "patch": { + "summary": "Partially modifies Jans authorization server Application configuration properties.", + "description": "Partially modifies Jans authorization server AppConfiguration properties.", + "operationId": "patch-properties", + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/jans-auth-server/config/properties.write" + ] + } + ], + "tags": [ + "Configuration \u2013 Properties" + ], + "requestBody": { + "content": { + "application/json-patch+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PatchRequest", + "description": "String representing patch-document.", + "example": "[ {op:replace, path: loggingLevel, value: DEBUG } ]" + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "AppConfiguration", + "description": "Jans authorization server config properties.", + "$ref": "#/components/schemas/AppConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/jans-config-api/api/v1/jans-auth-server/config/persistence": { + "get": { + "summary": "Returns persistence type configured for Jans authorization server.", + "description": "Returns persistence type configured for Jans authorization server.", + "operationId": "get-properties-persistence", + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/jans-auth-server/config/properties.readonly" + ] + } + ], + "tags": [ + "Configuration \u2013 Properties" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "PersistenceConfiguration", + "description": "Jans Authorization Persistence Configuration object.", + "$ref": "#/components/schemas/PersistenceConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/jans-config-api/fido2/config": { + "get": { + "summary": "Gets Jans Authorization Server Fido2 configuration properties.", + "description": "Gets Jans Authorization Server Fido2 configuration properties.", + "operationId": "get-properties-fido2", + "tags": [ + "Fido2 - Configuration" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JansFido2DynConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/fido2.readonly" + ] + } + ] + }, + "put": { + "summary": "Updates Fido2 configuration properties.", + "description": "Updates Fido2 configuration properties.", + "operationId": "put-properties-fido2", + "tags": [ + "Fido2 - Configuration" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JansFido2DynConfiguration" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JansFido2DynConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/fido2.write" + ] + } + ] + }, + "parameters": [] + }, + "/jans-config-api/fido2/registration/entries/{username}": { + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "description": "Username.", + "schema": { + "type": "string" + } + } + ], + "get": { + "summary": "Get details of connected FIDO2 devices registered to user.", + "description": "Get details of connected FIDO2 devices registered to user.", + "operationId": "get-registration-entries-fido2", + "tags": [ + "Fido2 - Configuration" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "FIDO2 registered devices", + "description": "List of all FIDO2 registered devices.", + "type": "array", + "items": { + "$ref": "#/components/schemas/Fido2RegistrationEntry" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/fido2.readonly" + ] + } + ] + } + }, + "/jans-config-api/api/v1/attributes": { + "get": { + "summary": "Gets a list of Gluu attributes.", + "description": "Gets all attributes. Optionally max-size of the result, attribute status and pattern can be provided.", + "operationId": "get-attributes", + "tags": [ + "Attribute" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "Gluu Attributes", + "description": "List of all attribute.", + "type": "array", + "items": { + "$ref": "#/components/schemas/GluuAttribute" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/attributes.readonly" + ] + } + ], + "parameters": [ + { + "schema": { + "type": "integer", + "default": 50 + }, + "in": "query", + "name": "limit", + "description": "Search size - max size of the results to return." + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "pattern", + "description": "Search pattern." + }, + { + "schema": { + "type": "string", + "default": "all" + }, + "in": "query", + "name": "status", + "description": "Status of the attribute" + } + ] + }, + "post": { + "summary": "Adds a new attribute.", + "description": "Adds a new attribute.", + "operationId": "post-attributes", + "tags": [ + "Attribute" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GluuAttribute" + } + } + } + }, + "responses": { + "201": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GluuAttribute" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/attributes.write" + ] + } + ] + }, + "put": { + "summary": "Updates an existing attribute.", + "description": "Updates an existing attribute.", + "operationId": "put-attributes", + "tags": [ + "Attribute" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GluuAttribute" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GluuAttribute" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/attributes.write" + ] + } + ] + }, + "parameters": [] + }, + "/jans-config-api/api/v1/attributes/{inum}": { + "parameters": [ + { + "name": "inum", + "in": "path", + "required": true, + "description": "Attribute ID.", + "schema": { + "type": "string" + } + } + ], + "get": { + "summary": "Gets an attribute based on inum.", + "description": "Gets an attribute based on inum.", + "operationId": "get-attributes-by-inum", + "tags": [ + "Attribute" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GluuAttribute" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/attributes.readonly" + ] + } + ] + }, + "delete": { + "summary": "Deletes an attribute based on inum.", + "description": "Deletes an attribute based on inum.", + "operationId": "delete-attributes-by-inum", + "tags": [ + "Attribute" + ], + "responses": { + "204": { + "description": "No Content" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/attributes.delete" + ] + } + ] + }, + "patch": { + "summary": "Partially modify a GluuAttribute.", + "description": "Partially modify a GluuAttribute.", + "operationId": "patch-attributes-by-inum", + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/attributes.write" + ] + } + ], + "tags": [ + "Attribute" + ], + "requestBody": { + "content": { + "application/json-patch+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PatchRequest", + "description": "String representing patch-document.", + "example": "[ {op:replace, path: displayName, value: \\\"CustomAttribute\\\" } ]" + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GluuAttribute" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/jans-config-api/api/v1/acrs": { + "get": { + "summary": "Gets default authentication method.", + "description": "Gets default authentication method.", + "operationId": "get-acrs", + "tags": [ + "Default Authentication Method" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationMethod" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/acrs.readonly" + ] + } + ] + }, + "put": { + "summary": "Updates default authentication method.", + "description": "Updates default authentication method.", + "operationId": "put-acrs", + "tags": [ + "Default Authentication Method" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationMethod" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationMethod" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/acrs.write" + ] + } + ] + }, + "parameters": [] + }, + "/jans-config-api/api/v1/config/database/ldap": { + "get": { + "summary": "Gets list of existing LDAP configurations.", + "description": "Gets list of existing LDAP configurations.", + "operationId": "get-config-database-ldap", + "tags": [ + "Database - LDAP configuration" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "LdapConfiguration", + "description": "List of configured LDAP configuration.", + "type": "array", + "items": { + "$ref": "#/components/schemas/LdapConfiguration" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/database/ldap.readonly" + ] + } + ] + }, + "post": { + "summary": "Adds a new LDAP configuration.", + "description": "Adds a new LDAP configuration.", + "operationId": "post-config-database-ldap", + "tags": [ + "Database - LDAP configuration" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LdapConfiguration" + } + } + } + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LdapConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/database/ldap.write" + ] + } + ] + }, + "put": { + "summary": "Updates LDAP configuration.", + "description": "Updates LDAP configuration.", + "operationId": "put-config-database-ldap", + "tags": [ + "Database - LDAP configuration" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LdapConfiguration" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LdapConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/database/ldap.write" + ] + } + ] + }, + "parameters": [] + }, + "/jans-config-api/api/v1/config/database/ldap/{name}": { + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "description": "Name of LDAP configuration.", + "schema": { + "type": "string" + } + } + ], + "get": { + "summary": "Gets an LDAP configuration by name.", + "description": "Gets an LDAP configuration by name.", + "operationId": "get-config-database-ldap-by-name", + "tags": [ + "Database - LDAP configuration" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LdapConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/database/ldap.readonly" + ] + } + ] + }, + "delete": { + "summary": "Deletes an LDAP configuration.", + "description": "Deletes an LDAP configuration.", + "operationId": "delete-config-database-ldap-by-name", + "tags": [ + "Database - LDAP configuration" + ], + "responses": { + "204": { + "description": "No Content" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/database/ldap.delete" + ] + } + ] + }, + "patch": { + "summary": "Partially modify an LDAP configuration.", + "description": "Partially modify an LDAP configuration.", + "operationId": "patch-config-database-ldap-by-name", + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/database/ldap.write" + ] + } + ], + "tags": [ + "Database - LDAP configuration" + ], + "requestBody": { + "content": { + "application/json-patch+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PatchRequest", + "description": "String representing patch-document.", + "example": "[ {op:replace, path: maxConnections, value: 8 } ]" + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LdapConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/jans-config-api/api/v1/config/database/ldap/test": { + "post": { + "summary": "Tests an LDAP configuration.", + "description": "Tests an LDAP configuration.", + "operationId": "post-config-database-ldap-test", + "tags": [ + "Database - LDAP configuration" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LdapConfiguration" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "boolean", + "description": "LDAP connection status true if connection is successfully established." + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/database/ldap.readonly" + ] + } + ] + } + }, + "/jans-config-api/api/v1/config/database/sql": { + "get": { + "summary": "Gets list of existing sql configurations.", + "description": "Gets list of existing sql configurations.", + "operationId": "get-config-database-sql", + "tags": [ + "Database - Sql configuration" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "SqlConfiguration", + "description": "List of configured Sql configuration.", + "items": { + "$ref": "#/components/schemas/SqlConfiguration" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/database/sql.readonly" + ] + } + ] + }, + "post": { + "summary": "Adds a new Sql configuration.", + "description": "Adds a new Sql configuration.", + "operationId": "post-config-database-sql", + "tags": [ + "Database - Sql configuration" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SqlConfiguration" + } + } + } + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SqlConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/database/sql.write" + ] + } + ] + }, + "put": { + "summary": "Updates Sql configuration.", + "description": "Updates Sql configuration.", + "operationId": "put-config-database-sql", + "tags": [ + "Database - Sql configuration" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SqlConfiguration" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SqlConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/database/sql.write" + ] + } + ] + }, + "parameters": [] + }, + "/jans-config-api/api/v1/config/database/sql/{name}": { + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "description": "Name of Sql configuration.", + "schema": { + "type": "string" + } + } + ], + "get": { + "summary": "Gets a Sql configurations by name.", + "description": "Gets a Sql configurations by name.", + "operationId": "get-config-database-sql-by-name", + "tags": [ + "Database - Sql configuration" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "SqlConfiguration", + "description": "List of configured Sql configuration.", + "$ref": "#/components/schemas/SqlConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/database/sql.readonly" + ] + } + ] + }, + "patch": { + "summary": "Partially modify an Sql configuration.", + "description": "Partially modify an Sql configuration.", + "operationId": "patch-config-database-sql-by-name", + "tags": [ + "Database - Sql configuration" + ], + "requestBody": { + "content": { + "application/json-patch+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PatchRequest", + "description": "String representing patch-document.", + "example": "[ {op:replace, path: maxConnections, value: 8 } ]" + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SqlConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/database/sql.write" + ] + } + ] + }, + "delete": { + "summary": "Deletes a Sql configurations by name.", + "description": "Deletes a Sql configurations by name.", + "operationId": "delete-config-database-sql-by-name", + "tags": [ + "Database - Sql configuration" + ], + "responses": { + "204": { + "description": "No Content" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/database/sql.delete" + ] + } + ] + } + }, + "/jans-config-api/api/v1/config/database/sql/test": { + "post": { + "summary": "Tests a Sql configuration.", + "description": "Tests a Sql configuration.", + "operationId": "post-config-database-sql-test", + "tags": [ + "Database - Sql configuration" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SqlConfiguration" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "boolean", + "description": "Sql connection status true if connection is successfully established." + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/database/sql.readonly" + ] + } + ] + } + }, + "/jans-config-api/api/v1/config/database/couchbase": { + "get": { + "summary": "Gets list of existing Couchbase configurations.", + "description": "Gets list of existing Couchbase configurations.", + "operationId": "get-config-database-couchbase", + "tags": [ + "Database - Couchbase configuration" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "CouchbaseConfiguration", + "description": "List of configured Couchbase configuration.", + "items": { + "$ref": "#/components/schemas/CouchbaseConfiguration" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/database/couchbase.readonly" + ] + } + ] + }, + "post": { + "summary": "Adds a new Couchbase configuration.", + "description": "Adds a new Couchbase configuration.", + "operationId": "post-config-database-couchbase", + "tags": [ + "Database - Couchbase configuration" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CouchbaseConfiguration" + } + } + } + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CouchbaseConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/database/couchbase.write" + ] + } + ] + }, + "put": { + "summary": "Updates Couchbase configuration.", + "description": "Updates Couchbase configuration.", + "operationId": "put-config-database-couchbase", + "tags": [ + "Database - Couchbase configuration" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CouchbaseConfiguration" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CouchbaseConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/database/couchbase.write" + ] + } + ] + }, + "parameters": [] + }, + "/jans-config-api/api/v1/config/database/couchbase/{name}": { + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "description": "Name of Couchbase configuration.", + "schema": { + "type": "string" + } + } + ], + "get": { + "summary": "Gets a Couchbase configurations by name.", + "description": "Gets a Couchbase configurations by name.", + "operationId": "get-config-database-couchbase-by-name", + "tags": [ + "Database - Couchbase configuration" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "CouchbaseConfiguration", + "description": "List of configured Couchbase configuration.", + "$ref": "#/components/schemas/CouchbaseConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/database/couchbase.readonly" + ] + } + ] + }, + "patch": { + "summary": "Partially modify an Couchbase configuration.", + "description": "Partially modify an Couchbase configuration.", + "operationId": "patch-config-database-couchbase-by-name", + "tags": [ + "Database - Couchbase configuration" + ], + "requestBody": { + "content": { + "application/json-patch+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PatchRequest", + "description": "String representing patch-document.", + "example": "[ {op:replace, path: maxConnections, value: 8 } ]" + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CouchbaseConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/database/couchbase.write" + ] + } + ] + }, + "delete": { + "summary": "Deletes a Couchbase configurations by name.", + "description": "Deletes a Couchbase configurations by name.", + "operationId": "delete-config-database-couchbase-by-name", + "tags": [ + "Database - Couchbase configuration" + ], + "responses": { + "204": { + "description": "No Content" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/database/couchbase.delete" + ] + } + ] + } + }, + "/jans-config-api/api/v1/config/database/couchbase/test": { + "post": { + "summary": "Tests a Couchbase configuration.", + "description": "Tests a Couchbase configuration.", + "operationId": "post-config-database-couchbase-test", + "tags": [ + "Database - Couchbase configuration" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CouchbaseConfiguration" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "boolean", + "description": "Couchbase connection status true if connection is successfully established." + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/database/couchbase.readonly" + ] + } + ] + } + }, + "/jans-config-api/api/v1/config/scripts": { + "get": { + "summary": "Gets a list of custom scripts.", + "description": "Gets a list of custom scripts.", + "operationId": "get-config-scripts", + "tags": [ + "Custom Scripts" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/CustomScript" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/scripts.readonly" + ] + } + ] + }, + "post": { + "summary": "Adds a new custom script.", + "description": "Adds a new custom script.", + "operationId": "post-config-scripts", + "tags": [ + "Custom Scripts" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomScript" + } + } + } + }, + "responses": { + "201": { + "description": "CREATED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomScript" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/scripts.write" + ] + } + ] + }, + "put": { + "summary": "Updates a custom script.", + "description": "Updates a custom script.", + "operationId": "put-config-scripts", + "x-cli-getdata": "get-config-scripts-by-inum", + "tags": [ + "Custom Scripts" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomScript" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomScript" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/scripts.write" + ] + } + ] + } + }, + "/jans-config-api/api/v1/config/scripts/type/{type}": { + "parameters": [ + { + "schema": { + "type": "string", + "enum": [ + "- person_authentication", + "- introspection", + "- resource_owner_password_credentials", + "- application_session", + "- cache_refresh", + "- client_registration", + "- id_generator", + "- uma_rpt_policy", + "- uma_rpt_claims", + "- uma_claims_gathering", + "- consent_gathering", + "- dynamic_scope", + "- spontaneous_scope", + "- end_session", + "- post_authn", + "- scim", + "- ciba_end_user_notification", + "- persistence_extension", + "- idp", + "revoke_token", + "discovery", + "update_token", + "config_api_auth" + ] + }, + "name": "type", + "in": "path", + "description": "Script type.", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "pattern", + "description": "Search pattern." + }, + { + "schema": { + "type": "integer", + "default": 50 + }, + "in": "query", + "name": "limit", + "description": "Search size - max size of the results to return." + } + ], + "get": { + "summary": "Gets list of scripts by type.", + "description": "Gets list of scripts by type.", + "operationId": "get-config-scripts-by-type", + "x-cli-ignore": true, + "tags": [ + "Custom Scripts" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/CustomScript" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/scripts.readonly" + ] + } + ] + } + }, + "/jans-config-api/api/v1/config/scripts/inum/{inum}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "inum", + "in": "path", + "required": true, + "description": "Script identifier." + } + ], + "get": { + "summary": "Gets a script by Inum.", + "description": "Gets a script by Inum.", + "operationId": "get-config-scripts-by-inum", + "x-cli-ignore": true, + "tags": [ + "Custom Scripts" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomScript" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/scripts.readonly" + ] + } + ] + } + }, + "/jans-config-api/api/v1/config/scripts/{inum}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "inum", + "in": "path", + "required": true, + "description": "Script identifier." + } + ], + "delete": { + "summary": "Deletes a custom script.", + "description": "Deletes a custom script.", + "operationId": "delete-config-scripts-by-inum", + "tags": [ + "Custom Scripts" + ], + "responses": { + "204": { + "description": "No Content" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not Found" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/scripts.delete" + ] + } + ] + }, + "patch": { + "summary": "Partially update custom script.", + "description": "Partially update custom script.", + "operationId": "patch-config-scripts-by-inum", + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/scripts.write" + ] + } + ], + "tags": [ + "Custom Scripts" + ], + "requestBody": { + "content": { + "application/json-patch+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PatchRequest", + "description": "String representing patch-document.", + "example": "[ {op:replace, path: enabled, value: \\\"false\\\" } ]" + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomScript" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/jans-config-api/api/v1/config/cache": { + "get": { + "summary": "Returns cache configuration.", + "description": "Returns cache configuration.", + "operationId": "get-config-cache", + "tags": [ + "Cache Configuration" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CacheConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/cache.readonly" + ] + } + ] + }, + "patch": { + "summary": "Partially modifies cache configuration.", + "description": "Partially modifies cache configuration.", + "operationId": "patch-config-cache", + "tags": [ + "Cache Configuration" + ], + "requestBody": { + "content": { + "application/json-patch+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PatchRequest", + "description": "String representing patch-document.", + "example": "[ {op:replace, path: memcachedConfiguration, value: response.memcachedConfiguration } ]" + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CacheConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/cache.write" + ] + } + ] + } + }, + "/jans-config-api/api/v1/config/cache/memcached": { + "get": { + "summary": "Returns Memcached cache configuration.", + "description": "Returns Memcached cache configuration.", + "operationId": "get-config-cache-memcached", + "tags": [ + "Cache Configuration \u2013 Memcached" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MemcachedConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/cache.readonly" + ] + } + ] + }, + "put": { + "summary": "Updates Memcached cache configuration.", + "description": "Updates Memcached cache configuration.", + "operationId": "put-config-cache-memcached", + "tags": [ + "Cache Configuration \u2013 Memcached" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MemcachedConfiguration" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MemcachedConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/cache.write" + ] + } + ] + }, + "parameters": [], + "patch": { + "summary": "Partially modifies Memcached cache configuration.", + "description": "Partially modifies Memcached cache configuration.", + "operationId": "patch-config-cache-memcached", + "tags": [ + "Cache Configuration \u2013 Memcached" + ], + "requestBody": { + "content": { + "application/json-patch+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PatchRequest", + "description": "String representing patch-document.", + "example": "[ {op:replace, path: memcachedConfiguration, value: response.memcachedConfiguration } ]" + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MemcachedConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/cache.write" + ] + } + ] + } + }, + "/jans-config-api/api/v1/config/cache/redis": { + "get": { + "summary": "Returns Redis cache configuration.", + "description": "Returns Redis cache configuration.", + "operationId": "get-config-cache-redis", + "tags": [ + "Cache Configuration \u2013 Redis" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RedisConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/cache.readonly" + ] + } + ] + }, + "put": { + "summary": "Updates Redis cache configuration.", + "description": "Updates Redis cache configuration.", + "operationId": "put-config-cache-redis", + "tags": [ + "Cache Configuration \u2013 Redis" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RedisConfiguration" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RedisConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/cache.write" + ] + } + ] + }, + "parameters": [], + "patch": { + "summary": "Partially modifies Redis cache configuration.", + "description": "Partially modifies Redis cache configuration.", + "operationId": "patch-config-cache-redis", + "tags": [ + "Cache Configuration \u2013 Redis" + ], + "requestBody": { + "content": { + "application/json-patch+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PatchRequest", + "description": "String representing patch-document.", + "example": "[ {op:replace, path: redisConfiguration/defaultPutExpiration, value: 80 } ]" + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RedisConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/cache.write" + ] + } + ] + } + }, + "/jans-config-api/api/v1/config/cache/in-memory": { + "get": { + "summary": "Returns in-Memory cache configuration.", + "description": "Returns in-Memory cache configuration.", + "operationId": "get-config-cache-in-memory", + "tags": [ + "Cache Configuration \u2013 in-Memory" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InMemoryConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/cache.readonly" + ] + } + ] + }, + "put": { + "summary": "Updates in-Memory cache configuration.", + "description": "Updates in-Memory cache configuration.", + "operationId": "put-config-cache-in-memory", + "tags": [ + "Cache Configuration \u2013 in-Memory" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InMemoryConfiguration" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InMemoryConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/cache.write" + ] + } + ] + }, + "parameters": [], + "patch": { + "summary": "Partially modifies In-Memory cache configuration.", + "description": "Partially modifies In-Memory cache configuration.", + "operationId": "patch-config-cache-in-memory", + "tags": [ + "Cache Configuration \u2013 in-Memory" + ], + "requestBody": { + "content": { + "application/json-patch+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PatchRequest", + "description": "String representing patch-document.", + "example": "[ {op:replace, path: inMemoryConfiguration/defaultPutExpiration, value: 80 } ]" + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InMemoryConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/cache.write" + ] + } + ] + } + }, + "/jans-config-api/api/v1/config/cache/native-persistence": { + "get": { + "summary": "Returns native persistence cache configuration.", + "description": "Returns native persistence cache configuration.", + "operationId": "get-config-cache-native-persistence", + "tags": [ + "Cache Configuration \u2013 Native-Persistence" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NativePersistenceConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/cache.readonly" + ] + } + ] + }, + "put": { + "summary": "Updates native persistence cache configuration.", + "description": "Updates native persistence cache configuration.", + "operationId": "put-config-cache-native-persistence", + "tags": [ + "Cache Configuration \u2013 Native-Persistence" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NativePersistenceConfiguration" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NativePersistenceConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/cache.write" + ] + } + ] + }, + "parameters": [], + "patch": { + "summary": "Partially modifies Native Persistence cache configuration.", + "description": "Partially modifies Native Persistence cache configuration.", + "operationId": "patch-config-cache-native-persistence", + "tags": [ + "Cache Configuration \u2013 Native-Persistence" + ], + "requestBody": { + "content": { + "application/json-patch+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PatchRequest", + "description": "String representing patch-document.", + "example": "[ {op:replace, path: nativePersistenceConfiguration/defaultPutExpiration, value: 80 } ]" + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NativePersistenceConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/cache.write" + ] + } + ] + } + }, + "/jans-config-api/api/v1/config/smtp": { + "get": { + "summary": "Returns SMTP server configuration.", + "description": "Returns SMTP server configuration.", + "operationId": "get-config-smtp", + "tags": [ + "Configuration \u2013 SMTP" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SmtpConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/smtp.readonly" + ] + } + ] + }, + "post": { + "summary": "Adds SMTP server configuration.", + "description": "Adds SMTP server configuration.", + "operationId": "post-config-smtp", + "tags": [ + "Configuration \u2013 SMTP" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SmtpConfiguration" + } + } + } + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SmtpConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/smtp.write" + ] + } + ] + }, + "put": { + "summary": "Updates SMTP server configuration.", + "description": "Updates SMTP server configuration.", + "operationId": "put-config-smtp", + "tags": [ + "Configuration \u2013 SMTP" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SmtpConfiguration" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SmtpConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/smtp.write" + ] + } + ] + }, + "delete": { + "summary": "Deletes SMTP server configuration.", + "description": "Deletes SMTP server configuration.", + "operationId": "delete-config-smtp", + "tags": [ + "Configuration \u2013 SMTP" + ], + "responses": { + "204": { + "description": "No Content" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/smtp.delete" + ] + } + ] + }, + "parameters": [] + }, + "/jans-config-api/api/v1/config/smtp/test": { + "post": { + "summary": "Test SMTP server configuration.", + "description": "Test SMTP server configuration.", + "operationId": "test-config-smtp", + "tags": [ + "Configuration \u2013 SMTP" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "boolean", + "description": "SMTP test status true if email sent is successful." + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/smtp.readonly" + ] + } + ] + } + }, + "/jans-config-api/api/v1/logging": { + "get": { + "tags": [ + "Configuration \u2013 Logging" + ], + "summary": "Returns Jans Authorization Server logging settings.", + "description": "Returns Jans Authorization Server logging settings.", + "operationId": "get-config-logging", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LoggingConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/logging.readonly" + ] + } + ] + }, + "put": { + "tags": [ + "Configuration \u2013 Logging" + ], + "summary": "Updates Jans Authorization Server logging settings.", + "description": "Updates Jans Authorization Server logging settings.", + "operationId": "put-config-logging", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LoggingConfiguration" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LoggingConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/logging.write" + ] + } + ] + }, + "parameters": [] + }, + "/jans-config-api/api/v1/config/jwks": { + "get": { + "tags": [ + "Configuration \u2013 JWK - JSON Web Key (JWK)" + ], + "summary": "Gets list of JSON Web Key (JWK) used by server.", + "description": "Gets list of JSON Web Key (JWK) used by server. JWK is a JSON data structure that represents a set of public keys as a JSON object [RFC4627].", + "operationId": "get-config-jwks", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebKeysConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/jwks.readonly" + ] + } + ] + }, + "put": { + "tags": [ + "Configuration \u2013 JWK - JSON Web Key (JWK)" + ], + "summary": "Puts/replaces JWKS", + "description": "Puts/replaces JSON Web Keys (JWKS).", + "operationId": "put-config-jwks", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebKeysConfiguration" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebKeysConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/jwks.write" + ] + } + ] + }, + "patch": { + "tags": [ + "Configuration \u2013 JWK - JSON Web Key (JWK)" + ], + "summary": "Patch JWKS", + "description": "Patch JSON Web Keys (JWKS).", + "operationId": "patch-config-jwks", + "requestBody": { + "content": { + "application/json-patch+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PatchRequest", + "description": "String representing patch-document." + }, + "example": "[{\"op\": \"add\",\"path\": \"/keys/1\", \"value\": { \"kty\": \"RSA\", \"e\": \"AQAB\",\"use\": \"sig\",\"crv\": \"\",\"kid\": \"dd570bfb-276a-44aa-a97d-667b57587108_sig_rs256\",\"x5c\": [\"MIIDBDCC...\"],\"exp\": 1599751946863,\"alg\": \"RS256\",\"n\": \"zj1NE..\"}}]" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebKeysConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/jwks.write" + ] + } + ] + } + }, + "/jans-config-api/api/v1/config/jwks/key": { + "post": { + "tags": [ + "Configuration \u2013 JWK - JSON Web Key (JWK)" + ], + "summary": "Adds a new key to JSON Web Keys (JWKS)", + "description": "Adds a new key to JSON Web Keys (JWKS).", + "operationId": "post-config-jwks-key", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JsonWebKey" + } + } + } + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JsonWebKey" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/jwks.write" + ] + } + ] + } + }, + "/jans-config-api/api/v1/config/jwks/{kid}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "kid", + "in": "path", + "description": "The unique identifier for the key.", + "required": true + } + ], + "get": { + "tags": [ + "Configuration \u2013 JWK - JSON Web Key (JWK)" + ], + "summary": "Get a JSON Web Key based on kid", + "description": "Get a JSON Web Key based on kid", + "operationId": "put-config-jwk-kid", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JsonWebKey" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/jwks.readonly" + ] + } + ] + }, + "patch": { + "tags": [ + "Configuration \u2013 JWK - JSON Web Key (JWK)" + ], + "summary": "Patch a specific JSON Web Key based on kid", + "description": "Patch a specific JSON Web Key based on kid", + "operationId": "patch-config-jwk-kid", + "requestBody": { + "content": { + "application/json-patch+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PatchRequest", + "description": "String representing patch-document." + }, + "example": "[{\"op\": \"add\",\"path\": \"/kty\", \"value\": \"RSA\"}]" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JsonWebKey" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/jwks.write" + ] + } + ] + }, + "delete": { + "tags": [ + "Configuration \u2013 JWK - JSON Web Key (JWK)" + ], + "summary": "Delete a JSON Web Key based on kid", + "description": "Delete a JSON Web Key based on kid", + "operationId": "delete-config-jwk-kid", + "responses": { + "204": { + "description": "No Content" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/jwks.write" + ] + } + ] + } + }, + "/jans-config-api/api/v1/openid/clients": { + "get": { + "tags": [ + "OAuth - OpenID Connect - Clients" + ], + "summary": "Gets list of OpenID Connect clients", + "description": "Gets list of OpenID Connect clients", + "operationId": "get-oauth-openid-clients", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "OpenID Clients.", + "description": "List of OpenID clients.", + "items": { + "$ref": "#/components/schemas/Client" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/openid/clients.readonly" + ] + } + ], + "parameters": [ + { + "schema": { + "type": "integer", + "default": 50 + }, + "in": "query", + "name": "limit", + "description": "Search size - max size of the results to return." + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "pattern", + "description": "Search pattern." + }, + { + "schema": { + "type": "integer", + "default": 1 + }, + "in": "query", + "name": "startIndex", + "description": "The 1-based index of the first query result." + }, + { + "schema": { + "type": "string", + "default": "inum" + }, + "in": "query", + "name": "sortBy", + "description": "Attribute whose value will be used to order the returned response." + }, + { + "schema": { + "type": "string", + "default": "ascending", + "enum": [ + "ascending", + "descending" + ] + }, + "in": "query", + "name": "sortOrder", + "description": "Order in which the sortBy param is applied. Allowed values are \"ascending\" and \"descending\"." + } + ] + }, + "post": { + "tags": [ + "OAuth - OpenID Connect - Clients" + ], + "summary": "Create new OpenId Connect client", + "description": "Create new OpenId Connect client", + "operationId": "post-oauth-openid-clients", + "requestBody": { + "content": { + "application/json": { + "schema": { + "title": "OpenID Connect Client Details.", + "description": "OpenID Connect Client Details.", + "$ref": "#/components/schemas/Client" + } + } + } + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "title": "OpenID Connect Client Details.", + "$ref": "#/components/schemas/Client" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/openid/clients.write" + ] + } + ] + }, + "put": { + "tags": [ + "OAuth - OpenID Connect - Clients" + ], + "summary": "Update OpenId Connect client.", + "description": "Update OpenId Connect client.", + "operationId": "put-oauth-openid-clients", + "requestBody": { + "content": { + "application/json": { + "schema": { + "title": "OpenID Connect Client Details.", + "$ref": "#/components/schemas/Client" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "OpenID Connect Client Details.", + "$ref": "#/components/schemas/Client" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/openid/clients.write" + ] + } + ] + } + }, + "/jans-config-api/api/v1/openid/clients/{inum}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "inum", + "in": "path", + "description": "Client identifier", + "required": true + } + ], + "get": { + "tags": [ + "OAuth - OpenID Connect - Clients" + ], + "summary": "Get OpenId Connect Client by Inum", + "description": "Get OpenId Connect Client by Inum.", + "operationId": "get-oauth-openid-clients-by-inum", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Client" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/openid/clients.readonly" + ] + } + ] + }, + "delete": { + "tags": [ + "OAuth - OpenID Connect - Clients" + ], + "summary": "Delete OpenId Connect client.", + "description": "Delete OpenId Connect client.", + "operationId": "delete-oauth-openid-clients-by-inum", + "responses": { + "204": { + "description": "No Content" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/openid/clients.delete" + ] + } + ] + }, + "patch": { + "tags": [ + "OAuth - OpenID Connect - Clients" + ], + "summary": "Update modified properties of OpenId Connect client by Inum.", + "description": "Update modified properties of OpenId Connect client by Inum.", + "operationId": "patch-oauth-openid-clients-by-inum", + "requestBody": { + "content": { + "application/json-patch+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PatchRequest", + "description": "String representing patch-document.", + "example": "[ {op:replace, path: backchannel_authentication_request_signing_alg, value: false } ]" + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "Client Details.", + "$ref": "#/components/schemas/Client" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/openid/clients.write" + ] + } + ] + } + }, + "/jans-config-api/api/v1/uma/resources": { + "get": { + "tags": [ + "OAuth - UMA Resources" + ], + "summary": "Gets list of UMA resources.", + "description": "Gets list of UMA resources.", + "operationId": "get-oauth-uma-resources", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "UMA Resource list.", + "description": "List of UMA Resource.", + "items": { + "$ref": "#/components/schemas/UmaResource" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/uma/resources.readonly" + ] + } + ], + "parameters": [ + { + "schema": { + "type": "integer", + "default": 50 + }, + "in": "query", + "name": "limit", + "description": "Search size - max size of the results to return." + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "pattern", + "description": "Search pattern." + } + ] + }, + "post": { + "tags": [ + "OAuth - UMA Resources" + ], + "summary": "Creates an UMA resource.", + "description": "Creates an UMA resource.", + "operationId": "post-oauth-uma-resources", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UmaResource" + } + } + } + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "title": "UMAResource", + "$ref": "#/components/schemas/UmaResource" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/uma/resources.write" + ] + } + ] + }, + "put": { + "tags": [ + "OAuth - UMA Resources" + ], + "summary": "Updates an UMA resource.", + "description": "Updates an UMA resource.", + "operationId": "put-oauth-uma-resources", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UmaResource" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UmaResource" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/uma/resources.write" + ] + } + ] + } + }, + "/jans-config-api/api/v1/uma/resources/{id}": { + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Resource description ID.", + "schema": { + "type": "string" + } + } + ], + "get": { + "tags": [ + "OAuth - UMA Resources" + ], + "summary": "Gets an UMA resource by ID.", + "description": "Gets an UMA resource by ID.", + "operationId": "get-oauth-uma-resources-by-id", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "UMAResource", + "$ref": "#/components/schemas/UmaResource" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/uma/resources.readonly" + ] + } + ] + }, + "delete": { + "tags": [ + "OAuth - UMA Resources" + ], + "summary": "Deletes an UMA resource.", + "description": "Deletes an UMA resource.", + "operationId": "delete-oauth-uma-resources-by-id", + "responses": { + "204": { + "description": "No Content" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/uma/resources.delete" + ] + } + ] + }, + "patch": { + "tags": [ + "OAuth - UMA Resources" + ], + "summary": "Partially updates an UMA resource by Inum.", + "description": "Partially updates an UMA resource by Inum.", + "operationId": "patch-oauth-uma-resources-by-id", + "requestBody": { + "content": { + "application/json-patch+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PatchRequest", + "description": "String representing patch-document.", + "example": "[ {op:replace, path: clients, value: [\\\"client_1\\\",\\\"client_2\\\"] },{op:add, path: clients/2, value: \\\"client_3\\\" } ]" + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "UMAResource", + "$ref": "#/components/schemas/UmaResource" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/uma/resources.write" + ] + } + ] + } + }, + "/jans-config-api/api/v1/scopes": { + "get": { + "tags": [ + "OAuth - Scopes" + ], + "summary": "Gets list of Scopes.", + "description": "Gets list of Scopes. Optionally type to filter the scope, max-size of the result and pattern can be provided.", + "operationId": "get-oauth-scopes", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "Scope description list.", + "description": "List of scope description.", + "items": { + "$ref": "#/components/schemas/Scope" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/scopes.readonly" + ] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "enum": [ + "openid", + "dynamic", + "uma", + "spontaneous", + "oauth" + ] + }, + "in": "query", + "name": "type", + "description": "Scope type." + }, + { + "schema": { + "type": "integer", + "default": 50 + }, + "in": "query", + "name": "limit", + "description": "Search size - max size of the results to return." + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "pattern", + "description": "Search pattern." + } + ] + }, + "post": { + "tags": [ + "OAuth - Scopes" + ], + "summary": "Create Scope.", + "description": "Create Scope.", + "operationId": "post-oauth-scopes", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Scope" + } + } + } + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Scope" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/scopes.write" + ] + } + ] + }, + "put": { + "tags": [ + "OAuth - Scopes" + ], + "summary": "Updates existing Scope.", + "description": "Updates existing Scope.", + "operationId": "put-oauth-scopes", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Scope" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Scope" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/scopes.write" + ] + } + ] + }, + "parameters": [] + }, + "/jans-config-api/api/v1/scopes/{inum}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "inum", + "in": "path", + "required": true + } + ], + "get": { + "tags": [ + "OAuth - Scopes" + ], + "summary": "Get Scope by Inum", + "description": "Get Scope by Inum", + "operationId": "get-oauth-scopes-by-inum", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Scope" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/scopes.readonly" + ] + } + ] + }, + "delete": { + "tags": [ + "OAuth - Scopes" + ], + "summary": "Delete Scope.", + "description": "Delete Scope.", + "operationId": "delete-oauth-scopes-by-inum", + "responses": { + "204": { + "description": "No Content" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/scopes.delete" + ] + } + ] + }, + "patch": { + "tags": [ + "OAuth - Scopes" + ], + "summary": "Update modified attributes of existing Scope by Inum.", + "description": "Update modified attributes of existing Scope by Inum.", + "operationId": "patch-oauth-scopes-by-id", + "requestBody": { + "content": { + "application/json-patch+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PatchRequest", + "description": "String representing patch-document.", + "example": "[ {op:replace, path: clients, value: [\\\"client_1\\\",\\\"client_2\\\"] },{op:add, path: clients/2, value: \\\"client_3\\\" } ]" + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Scope" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/scopes.write" + ] + } + ] + } + }, + "/jans-config-api/api/v1/stat": { + "get": { + "summary": "Provides server with basic statistic.", + "description": "Provides server with basic statistic.", + "operationId": "get-stat", + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/stats.readonly jans_stat" + ] + } + ], + "tags": [ + "Statistics - User" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "FlatStatResponse", + "description": "Jans Authorization Server statistic data.", + "type": "array", + "items": { + "$ref": "#/components/schemas/StatResponseItem" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "month", + "description": "Month for which the stat report is to be fetched. The parameter is mandatory if start_month and end_month parameters are not present.", + "example": "202012 (2020 Dec) 202101 (2021 Jan))", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "start_month", + "description": "Start-Month for which the stat report is to be fetched." + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "end_month", + "description": "End-Month for which the stat report is to be fetched." + }, + { + "schema": { + "type": "string", + "enum": [ + "json", + "openmetrics" + ], + "default": "json" + }, + "in": "query", + "name": "format", + "description": "Report format" + } + ] + } + }, + "/jans-config-api/api/v1/health": { + "get": { + "summary": "Returns application health status.", + "description": "Returns application health status.", + "operationId": "get-config-health", + "tags": [ + "Health - Check" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HealthStatus" + } + } + } + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/jans-config-api/api/v1/health/live": { + "get": { + "summary": "Returns application liveness status.", + "description": "Returns application liveness status.", + "operationId": "get-config-health-live", + "tags": [ + "Health - Check" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HealthStatusItem" + } + } + } + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/jans-config-api/api/v1/health/ready": { + "get": { + "summary": "Returns application readiness status.", + "description": "Returns application readiness status.", + "operationId": "get-config-health-ready", + "tags": [ + "Health - Check" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HealthStatusItem" + } + } + } + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/jans-config-api/api/v1/health/server-stat": { + "get": { + "summary": "Returns application server status.", + "description": "Returns application server status.", + "operationId": "get-server-stat", + "tags": [ + "Server Stats" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StatsData" + } + } + } + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/jans-config-api/mgt/configuser": { + "get": { + "tags": [ + "Configuration \u2013 User Management" + ], + "summary": "Gets list of users", + "description": "Gets list of users", + "operationId": "get-user", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "Users.", + "description": "List of users.", + "items": { + "$ref": "#/components/schemas/CustomUser" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/user.readonly" + ] + } + ], + "parameters": [ + { + "schema": { + "type": "integer", + "default": 50 + }, + "in": "query", + "name": "limit", + "description": "Search size - max size of the results to return." + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "pattern", + "description": "Search pattern." + }, + { + "schema": { + "type": "integer", + "default": 1 + }, + "in": "query", + "name": "startIndex", + "description": "The 1-based index of the first query result." + }, + { + "schema": { + "type": "string", + "default": "inum" + }, + "in": "query", + "name": "sortBy", + "description": "Attribute whose value will be used to order the returned response." + }, + { + "schema": { + "type": "string", + "default": "ascending", + "enum": [ + "ascending", + "descending" + ] + }, + "in": "query", + "name": "sortOrder", + "description": "Order in which the sortBy param is applied. Allowed values are \"ascending\" and \"descending\"." + } + ] + }, + "post": { + "tags": [ + "Configuration \u2013 User Management" + ], + "summary": "Create new User", + "description": "Create new User", + "operationId": "post-user", + "requestBody": { + "content": { + "application/json": { + "schema": { + "title": "User Details.", + "description": "User Details.", + "$ref": "#/components/schemas/ExtendedCustomUser" + } + } + } + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "title": "User Details.", + "$ref": "#/components/schemas/CustomUser" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/user.write" + ] + } + ] + }, + "put": { + "tags": [ + "Configuration \u2013 User Management" + ], + "summary": "Update User.", + "description": "Update User.", + "operationId": "put-user", + "requestBody": { + "content": { + "application/json": { + "schema": { + "title": "User Details.", + "$ref": "#/components/schemas/CustomUser" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "User Details.", + "$ref": "#/components/schemas/CustomUser" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/user.write" + ] + } + ] + } + }, + "/jans-config-api/mgt/configuser/{inum}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "inum", + "in": "path", + "description": "User identifier", + "required": true + } + ], + "get": { + "tags": [ + "Configuration \u2013 User Management" + ], + "summary": "Get User by Inum", + "description": "Get User by Inum.", + "operationId": "get-user-by-inum", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomUser" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/user.readonly" + ] + } + ] + }, + "delete": { + "tags": [ + "Configuration \u2013 User Management" + ], + "summary": "Delete User.", + "description": "Delete User.", + "operationId": "delete-user", + "responses": { + "204": { + "description": "No Content" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/user.delete" + ] + } + ] + }, + "patch": { + "tags": [ + "Configuration \u2013 User Management" + ], + "summary": "Patch user properties by Inum.", + "description": "Patch user properties by Inum.", + "operationId": "patch-user-by-inum", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "$ref": "#/components/schemas/UserPatchRequest", + "description": "Patch request object", + "example": "[ {\"jsonPatchString\": {\"op\": \"add\", \"path\": \"userId\",\"value\": \"test-user\" }, \"customAttributes\": [{\"name\": \"name, displayName, birthdate, email\",\"multiValued\": true,\"values\": [\"string\"]}]}]" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "User Details.", + "$ref": "#/components/schemas/CustomUser" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "description": "Internal Server Error" + } + }, + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/user.write" + ] + } + ] + } + }, + "/jans-config-api/scim/config": { + "get": { + "summary": "Retrieves SCIM App configuration.", + "description": "Retrieves SCIM configuration.", + "operationId": "get-scim-config", + "security": [ + { + "oauth2": [ + "https://jans.io/scim/config.readonly" + ] + } + ], + "tags": [ + "SCIM - Config Management" + ], + "x-cli-plugin": "scim", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "ScimAppConfiguration", + "description": "SCIM App configuration.", + "$ref": "#/components/schemas/ScimAppConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + }, + "patch": { + "summary": "Partially modifies SCIM App configuration.", + "description": "Partially modifies SCIM App configuration.", + "operationId": "patch-scim-config", + "security": [ + { + "oauth2": [ + "https://jans.io/scim/config.write" + ] + } + ], + "tags": [ + "SCIM - Config Management" + ], + "x-cli-plugin": "scim", + "requestBody": { + "content": { + "application/json-patch+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PatchRequest", + "description": "String representing patch-document.", + "example": "[ {op:replace, path: loggingLevel, value: DEBUG } ]" + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "ScimAppConfiguration", + "description": "SCIM App configuration.", + "$ref": "#/components/schemas/ScimAppConfiguration" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/jans-config-api/api/v1/org": { + "get": { + "summary": "Retrieves organization configuration.", + "description": "Retrieves organization configuration.", + "operationId": "get-organization-config", + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/organization.readonly" + ] + } + ], + "tags": [ + "Organization Configuration" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "Organization", + "description": "Organization configuration.", + "$ref": "#/components/schemas/Organization" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + }, + "patch": { + "summary": "Partially modifies organization configuration.", + "description": "Partially modifies organization configuration.", + "operationId": "patch-organization-config", + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/config/organization.write" + ] + } + ], + "tags": [ + "Organization Configuration" + ], + "requestBody": { + "content": { + "application/json-patch+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PatchRequest", + "description": "String representing patch-document.", + "example": "[{\"op\": \"add\", \"path\": \"/jsFaviconPath\", \"value\": \"/opt/jans/jetty/jans-auth/custom/static/\"}]" + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "Organization", + "description": "Organization configuration.", + "$ref": "#/components/schemas/Organization" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/jans-config-api/api/v1/jans-auth-server/health": { + "get": { + "summary": "Returns auth server health status.", + "description": "Returns auth server health status.", + "operationId": "get-auth-server-health", + "tags": [ + "Auth Server Health - Check" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthHealthStatus" + } + } + } + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/jans-config-api/admin-ui/user/roles": { + "get": { + "tags": [ + "Admin UI - Role" + ], + "x-cli-plugin": "admin-ui", + "summary": "Get all admin ui roles.", + "description": "Get all admin ui roles.", + "operationId": "get-adminui-roles", + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/jans-auth-server/config/adminui/user/role.read" + ] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "Get admin ui roles.", + "description": "Get admin ui roles.", + "type": "array", + "items": { + "$ref": "#/components/schemas/AdminRole" + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/NotAcceptable" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + }, + "post": { + "tags": [ + "Admin UI - Role" + ], + "x-cli-plugin": "admin-ui", + "summary": "Add admin ui role.", + "description": "Add admin ui role.", + "operationId": "add-adminui-role", + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/jans-auth-server/config/adminui/user/role.write" + ] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "required": [ + "role" + ], + "$ref": "#/components/schemas/AdminRole" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "Add admin ui role.", + "description": "Add admin ui role.", + "type": "array", + "items": { + "$ref": "#/components/schemas/AdminRole" + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/NotAcceptable" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + }, + "put": { + "tags": [ + "Admin UI - Role" + ], + "x-cli-plugin": "admin-ui", + "summary": "Edit admin ui role.", + "description": "Edit admin ui role.", + "operationId": "edit-adminui-role", + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/jans-auth-server/config/adminui/user/role.write" + ] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminRole" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "Edit admin ui role.", + "description": "Edit admin ui role.", + "type": "array", + "items": { + "$ref": "#/components/schemas/AdminRole" + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/NotAcceptable" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + }, + "delete": { + "tags": [ + "Admin UI - Role" + ], + "x-cli-plugin": "admin-ui", + "summary": "Delete admin ui role.", + "description": "Delete admin ui role.", + "operationId": "delete-adminui-role", + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/jans-auth-server/config/adminui/user/role.write" + ] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminRole" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "Delete admin ui role.", + "description": "Delete admin ui role.", + "type": "array", + "items": { + "$ref": "#/components/schemas/AdminRole" + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/NotAcceptable" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/jans-config-api/admin-ui/user/permissions": { + "get": { + "tags": [ + "Admin UI - Permission" + ], + "x-cli-plugin": "admin-ui", + "summary": "Get admin ui permissions.", + "description": "Get admin ui permissions.", + "operationId": "get-adminui-permissions", + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/jans-auth-server/config/adminui/user/permission.read" + ] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "Get admin ui permissions.", + "description": "Get admin ui permissions.", + "type": "array", + "items": { + "$ref": "#/components/schemas/AdminPermission" + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/NotAcceptable" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + }, + "post": { + "tags": [ + "Admin UI - Permission" + ], + "x-cli-plugin": "admin-ui", + "summary": "Add admin ui permission.", + "description": "Add admin ui permission.", + "operationId": "add-adminui-permission", + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/jans-auth-server/config/adminui/user/permission.write" + ] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "required": [ + "permission" + ], + "$ref": "#/components/schemas/AdminPermission" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "Add admin ui permission.", + "description": "Add admin ui permission.", + "type": "array", + "items": { + "$ref": "#/components/schemas/AdminPermission" + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/NotAcceptable" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + }, + "put": { + "tags": [ + "Admin UI - Permission" + ], + "x-cli-plugin": "admin-ui", + "summary": "Edit admin ui permission.", + "description": "Edit admin ui permission.", + "operationId": "edit-adminui-permission", + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/jans-auth-server/config/adminui/user/permission.write" + ] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminPermission" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "Edit admin ui permission.", + "description": "Edit admin ui permission.", + "type": "array", + "items": { + "$ref": "#/components/schemas/AdminPermission" + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/NotAcceptable" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + }, + "delete": { + "tags": [ + "Admin UI - Permission" + ], + "x-cli-plugin": "admin-ui", + "summary": "Delete admin ui permission.", + "description": "Delete admin ui permission.", + "operationId": "delete-adminui-permission", + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/jans-auth-server/config/adminui/user/permission.write" + ] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminPermission" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "Delete admin ui permission.", + "description": "Delete admin ui permission.", + "type": "array", + "items": { + "$ref": "#/components/schemas/AdminPermission" + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/NotAcceptable" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/jans-config-api/admin-ui/user/rolePermissionsMapping": { + "get": { + "tags": [ + "Admin UI - Role-Permissions Mapping" + ], + "x-cli-plugin": "admin-ui", + "summary": "Get admin ui role-permissions mapping.", + "description": "Get admin ui role-permissions mapping.", + "operationId": "get-adminui-role-permissions", + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/jans-auth-server/config/adminui/user/rolePermissionMapping.readonly" + ] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "Get admin ui role-permissions mapping.", + "description": "Get admin ui role-permissions mapping.", + "type": "array", + "items": { + "$ref": "#/components/schemas/RolePermissionMapping" + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/NotAcceptable" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + }, + "post": { + "tags": [ + "Admin UI - Role-Permissions Mapping" + ], + "x-cli-plugin": "admin-ui", + "summary": "Add role-permissions mapping.", + "description": "Add role-permissions mapping.", + "operationId": "Add role-permissions mapping.", + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/jans-auth-server/config/adminui/user/rolePermissionMapping.write" + ] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RolePermissionMapping" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "Add role-permissions mapping.", + "description": "Add role-permissions mapping.", + "type": "array", + "items": { + "$ref": "#/components/schemas/RolePermissionMapping" + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/NotAcceptable" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + }, + "put": { + "tags": [ + "Admin UI - Role-Permissions Mapping" + ], + "x-cli-plugin": "admin-ui", + "summary": "Map permissions to role.", + "description": "Map permissions to role.", + "operationId": "map-permissions-to-role", + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/jans-auth-server/config/adminui/user/rolePermissionMapping.write" + ] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RolePermissionMapping" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "Map permissions to role.", + "description": "Map permissions to role.", + "type": "array", + "items": { + "$ref": "#/components/schemas/RolePermissionMapping" + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/NotAcceptable" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + }, + "delete": { + "tags": [ + "Admin UI - Role-Permissions Mapping" + ], + "x-cli-plugin": "admin-ui", + "summary": "Remove role-permissions mapping.", + "description": "Remove role-permissions mapping.", + "operationId": "remove-role-permissions-permission", + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/jans-auth-server/config/adminui/user/rolePermissionMapping.write" + ] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RolePermissionMapping" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "Remove role-permissions mapping.", + "description": "Remove role-permissions mapping.", + "type": "array", + "items": { + "$ref": "#/components/schemas/RolePermissionMapping" + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/NotAcceptable" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + }, + "/jans-config-api/admin-ui/license/isActive": { + "get": { + "tags": [ + "Admin UI - License" + ], + "x-cli-plugin": "admin-ui", + "summary": "Check if admin-ui license is active.", + "description": "Check if admin-ui license is active.", + "operationId": "is-license-active", + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/jans-auth-server/config/adminui/license.readonly" + ] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "Check if admin-ui license is active.", + "description": "Check if admin-ui license is active.", + "$ref": "#/components/schemas/LicenseApiResponse" + } + } + } + }, + "400": { + "$ref": "#/components/schemas/LicenseApiResponse" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/schemas/LicenseApiResponse" + } + } + } + }, + "/jans-config-api/admin-ui/license/activateLicense": { + "post": { + "tags": [ + "Admin UI - License" + ], + "x-cli-plugin": "admin-ui", + "summary": "Activate license using license-key.", + "description": "Activate license using license-key.", + "operationId": "activate-adminui-license", + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/jans-auth-server/config/adminui/license.write" + ] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "required": [ + "licenseKey" + ], + "$ref": "#/components/schemas/LicenseApiRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "Activate license using license-key.", + "description": "Activate license using license-key.", + "$ref": "#/components/schemas/LicenseApiResponse" + } + } + } + }, + "400": { + "$ref": "#/components/schemas/LicenseApiResponse" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/schemas/LicenseApiResponse" + } + } + } + }, + "/jans-config-api/admin-ui/license/saveApiCredentials": { + "post": { + "tags": [ + "Admin UI - License" + ], + "x-cli-plugin": "admin-ui", + "summary": "Save license api credentials.", + "description": "Save license api credentials.", + "operationId": "save-license-api-credentials", + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/jans-auth-server/config/adminui/license.write" + ] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "required": [ + "licenseKey" + ], + "$ref": "#/components/schemas/LicenseSpringCredentials" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "Save license api credentials.", + "description": "Save license api credentials.", + "$ref": "#/components/schemas/LicenseApiResponse" + } + } + } + }, + "400": { + "$ref": "#/components/schemas/LicenseApiResponse" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/schemas/LicenseApiResponse" + } + } + } + }, + "/jans-config-api/admin-ui/license/licenseDetails": { + "get": { + "tags": [ + "Admin UI - License" + ], + "x-cli-plugin": "admin-ui", + "summary": "Get admin ui license details.", + "description": "Get admin ui license details.", + "operationId": "get-adminui-license", + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/jans-auth-server/config/adminui/license.readonly" + ] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "Get admin ui license details.", + "description": "Get admin ui license details.", + "$ref": "#/components/schemas/LicenseDetailsResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/NotAcceptable" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + }, + "put": { + "tags": [ + "Admin UI - License" + ], + "x-cli-plugin": "admin-ui", + "summary": "Edit admin ui license details.", + "description": "Edit admin ui license details.", + "operationId": "edit-adminui-license", + "security": [ + { + "oauth2": [ + "https://jans.io/oauth/jans-auth-server/config/adminui/license.write" + ] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "required": [ + "role" + ], + "$ref": "#/components/schemas/LicenseDetailsRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "title": "Edit admin ui license details.", + "description": "Edit admin ui license details.", + "$ref": "#/components/schemas/LicenseDetailsResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/NotAcceptable" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + } + } + } + }, + "components": { + "securitySchemes": { + "oauth2": { + "type": "oauth2", + "description": "Authorization", + "flows": { + "clientCredentials": { + "tokenUrl": "https://{op-hostname}/.../token", + "scopes": { + "https://jans.io/oauth/jans-auth-server/config/properties.readonly": "View Auth Server properties related information", + "https://jans.io/oauth/jans-auth-server/config/properties.write": "Manage Auth Server properties related information", + "https://jans.io/oauth/config/fido2.readonly": "View FIDO2 related information", + "https://jans.io/oauth/config/fido2.write": "Manage FIDO2 related information", + "https://jans.io/oauth/config/attributes.readonly": "View attribute related information", + "https://jans.io/oauth/config/attributes.write": "Manage attribute related information", + "https://jans.io/oauth/config/attributes.delete": "Delete attribute related information", + "https://jans.io/oauth/config/acrs.readonly": "View ACRS related information", + "https://jans.io/oauth/config/acrs.write": "Manage ACRS related information", + "https://jans.io/oauth/config/database/ldap.readonly": "View LDAP database related information", + "https://jans.io/oauth/config/database/ldap.write": "Manage LDAP database related information", + "https://jans.io/oauth/config/database/ldap.delete": "Delete LDAP database related information", + "https://jans.io/oauth/config/database/couchbase.readonly": "View Couchbase database information", + "https://jans.io/oauth/config/database/couchbase.write": "Manage Couchbase database related information", + "https://jans.io/oauth/config/database/couchbase.delete": "Delete Couchbase database related information", + "https://jans.io/oauth/config/scripts.readonly": "View cache scripts information", + "https://jans.io/oauth/config/scripts.write": "Manage scripts related information", + "https://jans.io/oauth/config/scripts.delete": "Delete scripts related information", + "https://jans.io/oauth/config/cache.readonly": "View cache related information", + "https://jans.io/oauth/config/cache.write": "Manage cache related information", + "https://jans.io/oauth/config/smtp.readonly": "View SMTP related information", + "https://jans.io/oauth/config/smtp.write": "Manage SMTP related information", + "https://jans.io/oauth/config/smtp.delete": "Delete SMTP related information", + "https://jans.io/oauth/config/logging.readonly": "View logging related information", + "https://jans.io/oauth/config/logging.write": "Manage logging related information", + "https://jans.io/oauth/config/jwks.readonly": "View JWKS related information", + "https://jans.io/oauth/config/jwks.write": "Manage JWKS related information", + "https://jans.io/oauth/config/openid/clients.readonly": "View clients related information", + "https://jans.io/oauth/config/openid/clients.write": "Manage clients related information", + "https://jans.io/oauth/config/openid/clients.delete": "Delete clients related information", + "https://jans.io/oauth/config/scopes.readonly": "View scope related information", + "https://jans.io/oauth/config/scopes.write": "Manage scope related information", + "https://jans.io/oauth/config/scopes.delete": "Delete scope related information", + "https://jans.io/oauth/config/uma/resources.readonly": "View UMA Resource related information", + "https://jans.io/oauth/config/uma/resources.write": "Manage UMA Resource related information", + "https://jans.io/oauth/config/uma/resources.delete": "Delete UMA Resource related information", + "https://jans.io/oauth/config/database/sql.readonly": "View SQL database related information", + "https://jans.io/oauth/config/database/sql.write": "Manage SQL database related information", + "https://jans.io/oauth/config/database/sql.delete": "Delete SQL database related information", + "https://jans.io/oauth/config/stats.readonly": "Vew server with basic statistic", + "https://jans.io/oauth/config/scim/users.read": "Vew scim user related information", + "https://jans.io/oauth/config/scim/users.write": "Manage scim user related information", + "https://jans.io/scim/config.readonly": "Vew SCIM App configuration", + "https://jans.io/scim/config.write": "Manage SCIM App configuration", + "https://jans.io/oauth/config/organization.readonly": "View organization configuration information", + "https://jans.io/oauth/config/organization.write": "Manage organization configuration information", + "https://jans.io/oauth/config/user.readonly": "View user related information", + "https://jans.io/oauth/config/user.write": "Manage user related information", + "https://jans.io/oauth/config/user.delete": "Delete user related information" + } + } + } + } + }, + "responses": { + "Found": { + "description": "Resource Found.", + "content": {} + }, + "InvalidRequest": { + "description": "Invalid parameters are provided to endpoint.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "Unauthorized": { + "description": "Access token is missing or invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "AccessDenied": { + "description": "Invalid details provided hence access denied.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "NotFound": { + "description": "Resource Not Found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "NotAcceptable": { + "description": "Request Not Acceptable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "InternalServerError": { + "description": "Internal error occurred. Please check log file for details.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "schemas": { + "ErrorResponse": { + "required": [ + "error_code", + "error_description" + ], + "type": "object", + "properties": { + "error_code": { + "type": "string" + }, + "error_description": { + "type": "string" + }, + "details": { + "type": "string" + } + } + }, + "CustomScript": { + "type": "object", + "description": "Script", + "required": [ + "name", + "script", + "scriptType", + "programmingLanguage", + "moduleProperties", + "level" + ], + "properties": { + "dn": { + "type": "string" + }, + "inum": { + "description": "XRI i-number. Identifier to uniquely identify the script.", + "type": "string" + }, + "name": { + "type": "string", + "pattern": "^[a-zA-Z0-9_\\\\-\\\\:\\\\/\\\\.]+$", + "minLength": 1, + "maxLength": 60, + "description": "Custom script name. Should contain only letters, digits and underscores." + }, + "aliases": { + "type": "array", + "description": "List of possible aliases for the custom script.", + "items": { + "type": "string" + } + }, + "description": { + "type": "string", + "description": "Details describing the script." + }, + "script": { + "type": "string", + "description": "Actual script." + }, + "scriptType": { + "type": "string", + "description": "Type of script.", + "enum": [ + "PERSON_AUTHENTICATION", + "INTROSPECTION", + "RESOURCE_OWNER_PASSWORD_CREDENTIALS", + "APPLICATION_SESSION", + "CACHE_REFRESH", + "CLIENT_REGISTRATION", + "ID_GENERATOR", + "UMA_RPT_POLICY", + "UMA_RPT_CLAIMS", + "UMA_CLAIMS_GATHERING", + "CONSENT_GATHERING", + "DYNAMIC_SCOPE", + "SPONTANEOUS_SCOPE", + "END_SESSION", + "POST_AUTHN", + "SCIM", + "CIBA_END_USER_NOTIFICATION", + "REVOKE_TOKEN", + "PERSISTENCE_EXTENSION", + "IDP", + "DISCOVERY", + "UPDATE_TOKEN", + "CONFIG_API" + ] + }, + "programmingLanguage": { + "type": "string", + "enum": [ + "PYTHON", + "JAVA" + ], + "description": "Programming language of the custom script." + }, + "moduleProperties": { + "type": "array", + "description": "Module-level properties applicable to the script.", + "items": { + "$ref": "#/components/schemas/SimpleCustomProperty" + } + }, + "configurationProperties": { + "type": "array", + "description": "Configuration properties applicable to the script.", + "items": { + "$ref": "#/components/schemas/SimpleExtendedCustomProperty" + } + }, + "level": { + "type": "integer", + "description": "Script level." + }, + "revision": { + "type": "integer", + "format": "int64", + "description": "Update revision number of the script.", + "default": 0 + }, + "enabled": { + "type": "boolean", + "description": "boolean value indicating if script enabled.", + "default": false + }, + "scriptError": { + "type": "object", + "description": "Possible errors assosiated with the script.", + "$ref": "#/components/schemas/ScriptError" + }, + "modified": { + "type": "boolean", + "description": "boolean value indicating if the script is modified.", + "default": false + }, + "internal": { + "type": "boolean", + "description": "boolean value indicating if the script is internal.", + "default": false + } + } + }, + "LdapConfiguration": { + "type": "object", + "required": [ + "configId", + "bindDN", + "maxConnections", + "primaryKey", + "localPrimaryKey", + "bindPassword", + "servers", + "baseDNs", + "useSSL" + ], + "properties": { + "configId": { + "type": "string", + "description": "Unique identifier - Name", + "example": "auth_ldap_server" + }, + "bindDN": { + "type": "string", + "description": "This contains the username to connect to the backend server. You need to use full DN here. As for example, cn=jans,dc=company,dc=org." + }, + "bindPassword": { + "type": "string", + "description": "Ldap password for binding." + }, + "servers": { + "type": "array", + "description": "List of LDAP authentication servers.", + "items": { + "type": "string", + "description": "Unique name of the authentication server and port number.", + "example": "authserver.org:63" + } + }, + "maxConnections": { + "type": "integer", + "description": "This value defines the maximum number of connections that are allowed to read the backend Active Directory/LDAP server.", + "format": "int32", + "default": 2 + }, + "useSSL": { + "type": "boolean", + "description": "Enable SSL communication between Jans Server and LDAP server." + }, + "baseDNs": { + "type": "array", + "description": "List contains the location of the Active Directory/LDAP tree from where the Gluu Server shall read the user information.", + "items": { + "type": "string" + } + }, + "primaryKey": { + "type": "string", + "description": "Used to search and bind operations in configured LDAP server.", + "example": "SAMAccountName,uid, email" + }, + "localPrimaryKey": { + "type": "string", + "description": "Used to search local user entry in Gluu Server\u2019s internal LDAP directory.", + "example": "uid, email" + }, + "useAnonymousBind": { + "type": "boolean", + "description": "Boolean value used to indicate if the LDAP Server will allow anonymous bind request.", + "default": false + }, + "enabled": { + "type": "boolean", + "description": "Boolean value used to indicate if the LDAP Server is enabled. Do not use this unless the server administrator has entered all the required values.", + "default": false + }, + "version": { + "type": "integer", + "description": "LDAP server version." + }, + "level": { + "type": "integer", + "description": "A string that indicates the level." + } + } + }, + "CouchbaseConfiguration": { + "type": "object", + "required": [ + "configId", + "userName", + "userPassword", + "servers", + "defaultBucket", + "buckets", + "passwordEncryptionMethod", + "sslTrustStoreFile", + "sslTrustStorePin", + "sslTrustStoreFormat" + ], + "properties": { + "configId": { + "type": "string", + "description": "Unique identifier" + }, + "userName": { + "type": "string", + "description": "Couchbase server user." + }, + "userPassword": { + "type": "string", + "description": "Encoded Couchbase server user password." + }, + "servers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Couchbase server host and port." + }, + "defaultBucket": { + "type": "string", + "description": "Main bucket that application should use if other mapping rules were not applied." + }, + "buckets": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of buckets defining mapping rules." + }, + "passwordEncryptionMethod": { + "type": "string", + "description": "A list of the password encryption algorithms.", + "enum": [ + "SHA", + "SSHA", + "SHA-256", + "SSHA-256", + "SHA-384", + "SSHA-384", + "SHA-512", + "SSHA-512", + "MD5", + "SMD5", + "CRYPT", + "CRYPT-MD5", + "CRYPT-SHA-256", + "CRYPT-SHA-512", + "CRYPT-BCRYPT", + "CRYPT-BCRYPT", + "PKCS5S2" + ] + }, + "operationTracingEnabled": { + "type": "boolean", + "description": "Boolean value True if tracing is enabled on the environment.", + "default": false + }, + "mutationTokensEnabled": { + "type": "boolean", + "description": "If mutation tokens are enabled, they can be used for advanced durability requirements, as well as optimized RYOW consistency." + }, + "connectTimeout": { + "type": "integer", + "description": "The default timeout for connection timeout.", + "format": "int32" + }, + "computationPoolSize": { + "type": "integer", + "format": "int32", + "description": "Sets the pool size (number of threads to use) for all non-blocking operations, default value is the number of CPUs." + }, + "useSSL": { + "type": "boolean", + "description": "Identifies if SSL should be enabled.", + "default": true + }, + "sslTrustStoreFile": { + "type": "string", + "description": "The path to the trust store file to use. It contains the trusted certificates." + }, + "sslTrustStorePin": { + "type": "string", + "description": "The PIN to use to access the contents of the trust store." + }, + "sslTrustStoreFormat": { + "type": "string", + "description": "The format to use for the trust store." + }, + "binaryAttributes": { + "type": "array", + "description": "List of binary attributes.", + "items": { + "type": "string" + } + }, + "certificateAttributes": { + "type": "array", + "description": "List of certificate attributes.", + "items": { + "type": "string" + } + } + } + }, + "SqlConfiguration": { + "type": "object", + "required": [ + "configId", + "userName", + "userPassword", + "connectionUri", + "schemaName", + "passwordEncryptionMethod" + ], + "properties": { + "configId": { + "type": "string", + "description": "Unique identifier" + }, + "userName": { + "type": "string", + "description": "Sql server user." + }, + "userPassword": { + "type": "string", + "description": "Encoded Sql server user password." + }, + "connectionUri": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Sql server connection Uri." + }, + "schemaName": { + "type": "string", + "description": "Database schema name." + }, + "passwordEncryptionMethod": { + "type": "string", + "description": "A list of the password encryption algorithms.", + "enum": [ + "SHA", + "SSHA", + "SHA-256", + "SSHA-256", + "SHA-384", + "SSHA-384", + "SHA-512", + "SSHA-512", + "MD5", + "SMD5", + "CRYPT", + "CRYPT-MD5", + "CRYPT-SHA-256", + "CRYPT-SHA-512", + "CRYPT-BCRYPT", + "CRYPT-BCRYPT", + "PKCS5S2" + ] + }, + "serverTimezone": { + "type": "string", + "description": "Database schema name." + }, + "binaryAttributes": { + "type": "array", + "description": "List of binary attributes.", + "items": { + "type": "string" + } + }, + "certificateAttributes": { + "type": "array", + "description": "List of certificate attributes.", + "items": { + "type": "string" + } + } + } + }, + "JsonWebKey": { + "type": "object", + "description": "JsonWebKey", + "required": [ + "kid", + "kty", + "use", + "alg", + "exp" + ], + "properties": { + "name": { + "type": "string", + "description": "Name of the key." + }, + "descr": { + "type": "string", + "description": "key description." + }, + "kid": { + "type": "string", + "description": "The unique identifier for the key." + }, + "kty": { + "type": "string", + "description": "The family of cryptographic algorithms used with the key." + }, + "use": { + "type": "string", + "description": "How the key was meant to be used; sig represents the signature." + }, + "alg": { + "type": "string", + "description": "The specific cryptographic algorithm used with the key." + }, + "crv": { + "type": "string", + "description": "The crv member identifies the cryptographic curve used with the key. Values defined by this specification are P-256, P-384 and P-521. Additional crv values MAY be used, provided they are understood by implementations using that Elliptic Curve key. The crv value is case sensitive." + }, + "exp": { + "type": "integer", + "format": "int64", + "description": "Contains the token expiration timestamp" + }, + "x5c": { + "type": "array", + "description": "The x.509 certificate chain. The first entry in the array is the certificate to use for token verification; the other certificates can be used to verify this first certificate.", + "items": { + "type": "string" + } + }, + "n": { + "type": "string", + "description": "The modulus for the RSA public key." + }, + "e": { + "type": "string", + "description": "The exponent for the RSA public key." + }, + "x": { + "type": "string", + "description": "The x member contains the x coordinate for the elliptic curve point. It is represented as the base64url encoding of the coordinate's big endian representation." + }, + "y": { + "type": "string", + "description": "The y member contains the y coordinate for the elliptic curve point. It is represented as the base64url encoding of the coordinate's big endian representation." + } + } + }, + "PersistenceConfiguration": { + "title": "PersistenceConfiguration", + "description": "Persistence configuration properties.", + "properties": { + "persistenceType": { + "type": "string", + "description": "Jans Auth Server persistence type configured.", + "enum": [ + "ldap", + "couchbase", + "sql", + "spanner", + "hybrid" + ] + } + } + }, + "AppConfiguration": { + "title": "AppConfiguration", + "description": "App configuration properties.", + "properties": { + "issuer": { + "type": "string", + "description": "URL using the https scheme that OP asserts as Issuer identifier.", + "example": "https://server.example.com/" + }, + "baseEndpoint": { + "type": "string", + "description": "The base URL for endpoints.", + "example": "https://server.example.com/restv1" + }, + "authorizationEndpoint": { + "type": "string", + "description": "The authorization endpoint URL.", + "example": "https://server.example.com/restv1/authorize" + }, + "tokenEndpoint": { + "type": "string", + "description": "The token endpoint URL.", + "example": "https://server.example.com/restv1/token" + }, + "tokenRevocationEndpoint": { + "type": "string", + "description": "The URL for the access_token or refresh_token revocation endpoint.", + "example": "https://server.example.com/restv1/revoke" + }, + "userInfoEndpoint": { + "type": "string", + "description": "The User Info endpoint URL.", + "example": "https://server.example.com/restv1/userinfo" + }, + "clientInfoEndpoint": { + "type": "string", + "description": "The Client Info endpoint URL.", + "example": "https://server.example.com/restv1/clientinfo" + }, + "checkSessionIFrame": { + "type": "string", + "description": "URL for an OP IFrame that supports cross-origin communications for session state information with the RP Client using the HTML5 postMessage API.", + "example": "https://server.example.com/opiframe.htm" + }, + "endSessionEndpoint": { + "type": "string", + "description": "URL at the OP to which an RP can perform a redirect to request that the end user be logged out at the OP.", + "example": "https://server.example.com/restv1/end_session" + }, + "jwksUri": { + "type": "string", + "description": "URL of the OP\\'s JSON Web Key Set (JWK) document. This contains the signing key(s) the RP uses to validate signatures from the OP.", + "example": "https://server.example.com/restv1/jwks" + }, + "registrationEndpoint": { + "type": "string", + "description": "URL of the Registration Endpoint.", + "example": "https://server.example.com/restv1/register" + }, + "openIdDiscoveryEndpoint": { + "type": "string", + "description": "URL for the Discovery Endpoint.", + "example": "https://server.example.com/.well-known/webfinger" + }, + "openIdConfigurationEndpoint": { + "type": "string", + "description": "URL for the Open ID Connect Configuration Endpoint.", + "example": "https://server.example.com/.well-known/openid-configuration" + }, + "idGenerationEndpoint": { + "type": "string", + "description": "URL for the ID Generation Endpoint.", + "example": "https://server.example.com/restv1/id" + }, + "introspectionEndpoint": { + "type": "string", + "description": "URL for the Introspection Endpoint.", + "example": "https://server.example.com/restv1/introspection" + }, + "deviceAuthzEndpoint": { + "type": "string", + "description": "URL for the Device Authorization.", + "example": "https://server.example.com/restv1/device_authorization" + }, + "sessionAsJwt": { + "type": "boolean", + "description": "Boolean value true saves session data as a JWT." + }, + "sectorIdentifierCacheLifetimeInMinutes": { + "type": "integer", + "description": "Sector Identifier cache lifetime in minutes." + }, + "umaConfigurationEndpoint": { + "type": "string", + "description": "URL for the UMA Configuration Endpoint.", + "example": "https://server.example.com/restv1/uma2-configuration" + }, + "umaRptAsJwt": { + "type": "boolean", + "description": "Issue RPT as JWT or as random string." + }, + "umaRptLifetime": { + "type": "integer", + "description": "UMA RPT lifetime." + }, + "umaTicketLifetime": { + "type": "integer", + "description": "UMA ticket lifetime." + }, + "umaPctLifetime": { + "type": "integer", + "description": "UMA PCT lifetime." + }, + "umaResourceLifetime": { + "type": "integer", + "description": "UMA PCT lifetime." + }, + "umaAddScopesAutomatically": { + "type": "boolean", + "description": "Add UMA scopes automatically if it is not registered yet." + }, + "umaValidateClaimToken": { + "type": "boolean", + "description": "Validate claim_token as id_token assuming it is issued by local idp." + }, + "umaGrantAccessIfNoPolicies": { + "type": "boolean", + "description": "Specifies whether to grant access to resources if there are no any policies associated with scopes." + }, + "umaRestrictResourceToAssociatedClient": { + "type": "boolean", + "description": "Restrict access to resource by associated client." + }, + "spontaneousScopeLifetime": { + "type": "integer", + "description": "The lifetime of spontaneous scope in seconds." + }, + "openidSubAttribute": { + "type": "string", + "description": "Specifies which LDAP attribute is used for the subject identifier claim.", + "example": "inum" + }, + "responseTypesSupported": { + "type": "array", + "description": "A list of the OAuth 2.0 response_type values that this OP supports.", + "items": { + "type": "string" + }, + "enum": [ + "code", + "token", + "id_token" + ] + }, + "responseModesSupported": { + "type": "array", + "description": "A list of the OAuth 2.0 Response Mode values that this OP supports.", + "items": { + "type": "string" + }, + "enum": [ + "query", + "fragment", + "form_post" + ] + }, + "grantTypesSupported": { + "type": "array", + "description": "A list of the OAuth 2.0 Grant Type values that this OP supports.", + "items": { + "type": "string" + }, + "enum": [ + "authorization_code", + "implicit", + "password", + "client_credentials", + "refresh_token", + "\\urn\\:ietf\\:params\\:oauth\\:grant-type\\:uma-ticket", + "\\urn\\:openid\\:params\\:grant-type\\:ciba" + ] + }, + "subjectTypesSupported": { + "type": "array", + "description": "A list of the Subject Identifier types that this OP supports. Valid types include pairwise and public.", + "items": { + "type": "string" + }, + "enum": [ + "public", + "pairwise" + ] + }, + "defaultSubjectType": { + "type": "string", + "description": "Default Subject Type used for Dynamic Client Registration.", + "enum": [ + "public", + "pairwise" + ] + }, + "userInfoSigningAlgValuesSupported": { + "type": "array", + "description": "A list of the JWS signing algorithms (alg values) JWA supported by the UserInfo Endpoint to encode the Claims in a JWT.", + "items": { + "type": "string" + }, + "enum": [ + "HS256", + "HS384", + "HS512", + "RS256", + "RS384", + "RS512", + "ES256", + "ES384", + "ES512", + "PS256", + "PS384", + "PS512" + ] + }, + "userInfoEncryptionAlgValuesSupported": { + "type": "array", + "description": "A list of the JWE encryption algorithms (alg values) JWA supported by the UserInfo Endpoint to encode the Claims in a JWT.", + "items": { + "type": "string" + }, + "enum": [ + "RSA1_5", + "RSA-OAEP", + "A128KW", + "A256KW" + ] + }, + "userInfoEncryptionEncValuesSupported": { + "type": "array", + "description": "A list of the JWE encryption algorithms (enc values) JWA supported by the UserInfo Endpoint to encode the Claims in a JWT.", + "items": { + "type": "string" + }, + "enum": [ + "A128CBC+HS256", + "A256CBC+HS512", + "A128GCM", + "A256GCM" + ] + }, + "idTokenSigningAlgValuesSupported": { + "type": "array", + "description": "A list of the JWS signing algorithms (alg values) supported by the OP for the ID Token to encode the Claims in a JWT.", + "items": { + "type": "string" + }, + "enum": [ + "none", + "HS256", + "HS384", + "HS512", + "RS256", + "RS384", + "RS512", + "ES256", + "ES384", + "ES512", + "PS256", + "PS384", + "PS512" + ] + }, + "idTokenEncryptionAlgValuesSupported": { + "type": "array", + "description": "A list of the JWE encryption algorithms (alg values) supported by the OP for the ID Token to encode the Claims in a JWT.", + "items": { + "type": "string" + }, + "enum": [ + "RSA1_5", + "RSA-OAEP", + "A128KW", + "A256KW" + ] + }, + "idTokenEncryptionEncValuesSupported": { + "type": "array", + "description": "A list of the JWE encryption algorithms (enc values) supported by the OP for the ID Token to encode the Claims in a JWT.", + "items": { + "type": "string" + }, + "enum": [ + "A128CBC+HS256", + "A256CBC+HS512", + "A128GCM", + "A256GCM" + ] + }, + "requestObjectSigningAlgValuesSupported": { + "type": "array", + "description": "A list of the JWS signing algorithms (alg values) supported by the OP for Request Objects.", + "items": { + "type": "string" + }, + "enum": [ + "none", + "HS256", + "HS384", + "HS512", + "RS256", + "RS384", + "RS512", + "ES256", + "ES384", + "ES512", + "PS256", + "PS384", + "PS512" + ] + }, + "requestObjectEncryptionAlgValuesSupported": { + "type": "array", + "description": "A list of the JWE encryption algorithms (alg values) supported by the OP for Request Objects.", + "items": { + "type": "string" + }, + "enum": [ + "RSA1_5", + "RSA-OAEP", + "A128KW", + "A256KW" + ] + }, + "requestObjectEncryptionEncValuesSupported": { + "type": "array", + "description": "A list of the JWE encryption algorithms (enc values) supported by the OP for Request Objects.", + "items": { + "type": "string" + }, + "enum": [ + "A128CBC+HS256", + "A256CBC+HS512", + "A128GCM", + "A256GCM" + ] + }, + "tokenEndpointAuthMethodsSupported": { + "type": "array", + "description": "A list of Client Authentication methods supported by this Token Endpoint.", + "items": { + "type": "string" + }, + "enum": [ + "client_secret_basic", + "client_secret_post", + "client_secret_jwt", + "private_key_jwt" + ] + }, + "tokenEndpointAuthSigningAlgValuesSupported": { + "type": "array", + "description": "A list of the JWS signing algorithms (alg values) supported by the Token Endpoint for the signature on the JWT used to authenticate the Client at the Token Endpoint for the private_key_jwt and client_secret_jwt authentication methods.", + "items": { + "type": "string" + }, + "enum": [ + "HS256", + "HS384", + "HS512", + "RS256", + "RS384", + "RS512", + "ES256", + "ES384", + "ES512", + "PS256", + "PS384", + "PS512" + ] + }, + "dynamicRegistrationCustomAttributes": { + "type": "array", + "description": "Custom attributes for the Dynamic registration.", + "items": { + "type": "string" + }, + "enum": [ + "jansTrustedClnt" + ] + }, + "displayValuesSupported": { + "type": "array", + "description": "A list of the display parameter values that the OpenID Provider supports.", + "items": { + "type": "string" + }, + "enum": [ + "page", + "popup" + ] + }, + "claimTypesSupported": { + "type": "array", + "description": "A list of the Claim Types that the OpenID Provider supports.", + "items": { + "type": "string" + }, + "enum": [ + "normal" + ] + }, + "jwksAlgorithmsSupported": { + "type": "array", + "description": "A list of algorithms that will be used in JWKS endpoint.", + "items": { + "type": "string" + }, + "enum": [ + "RS256", + "RS384", + "RS512", + "ES256", + "ES384", + "ES512", + "PS256", + "PS384", + "PS512", + "RSA1_5", + "RSA-OAEP" + ] + }, + "serviceDocumentation": { + "type": "string", + "description": "URL of a page containing human-readable information that developers might want or need to know when using the OpenID Provider.", + "format": "url", + "example": "http://gluu.org/docs" + }, + "claimsLocalesSupported": { + "type": "array", + "description": "Languages and scripts supported for values in Claims being returned.", + "items": { + "type": "string" + }, + "enum": [ + "en" + ] + }, + "idTokenTokenBindingCnfValuesSupported": { + "type": "array", + "description": "Array containing a list of the JWT Confirmation Method member names supported by the OP for Token Binding of ID Tokens. The presence of this parameter indicates that the OpenID Provider supports Token Binding of ID Tokens. If omitted, the default is that the OpenID Provider does not support Token Binding of ID Tokens.", + "items": { + "type": "string" + }, + "enum": [ + "tbh" + ] + }, + "uiLocalesSupported": { + "type": "array", + "description": "Languages and scripts supported for the user interface.", + "items": { + "type": "string" + }, + "enum": [ + "en", + "es" + ] + }, + "claimsParameterSupported": { + "type": "boolean", + "description": "Specifies whether the OP supports use of the claim\u2019s parameter." + }, + "requestParameterSupported": { + "type": "boolean", + "description": "Boolean value specifying whether the OP supports use of the request parameter." + }, + "requestUriParameterSupported": { + "type": "boolean", + "description": "Boolean value specifying whether the OP supports use of the request_uri parameter." + }, + "requestUriBlockList": { + "type": "array", + "description": "Block list for requestUri that can come to Authorization Endpoint (e.g. \"localhost\")", + "items": { + "type": "string" + } + }, + "requestUriHashVerificationEnabled": { + "type": "boolean", + "description": "Boolean value specifying whether the OP supports use of the request_uri hash verification." + }, + "requireRequestUriRegistration": { + "type": "boolean", + "description": "Boolean value specifying whether the OP requires any request_uri values used to be pre-registered using the request_uris registration parameter." + }, + "opPolicyUri": { + "type": "string", + "description": "URL that the OpenID Provider provides to the person registering the Client to read about the OP\\'s requirements on how the Relying Party can use the data provided by the OP.", + "example": "http://ox.gluu.org/doku.php?id=jans:policy" + }, + "opTosUri": { + "type": "string", + "description": "URL that the OpenID Provider provides to the person registering the Client to read about OpenID Provider's terms of service.", + "example": "http://ox.gluu.org/doku.php?id=jans:tos" + }, + "authorizationCodeLifetime": { + "type": "integer", + "description": "The lifetime of the Authorization Code." + }, + "refreshTokenLifetime": { + "type": "integer", + "description": "The lifetime of the Refresh Token." + }, + "idTokenLifetime": { + "type": "integer", + "description": "The lifetime of the ID Token.", + "example": 3600 + }, + "idTokenFilterClaimsBasedOnAccessToken": { + "type": "boolean", + "description": "Boolean value specifying whether idToken filters claims based on accessToken." + }, + "accessTokenLifetime": { + "type": "integer", + "description": "The lifetime of the short-lived Access Token.", + "example": 3600 + }, + "cleanServiceInterval": { + "type": "integer", + "description": "Time interval for the Clean Service in seconds.", + "example": 60 + }, + "cleanServiceBatchChunkSize": { + "type": "integer", + "description": "Each clean up iteration fetches chunk of expired data per base dn and removes it from storage.", + "example": 10000 + }, + "keyRegenerationEnabled": { + "type": "boolean", + "description": "Boolean value specifying whether to regenerate keys." + }, + "keyRegenerationInterval": { + "type": "integer", + "description": "The interval for key regeneration in hours.", + "example": 48 + }, + "defaultSignatureAlgorithm": { + "type": "string", + "description": "The default signature algorithm to sign ID Tokens.", + "enum": [ + "HS256", + "HS384", + "HS512", + "RS256", + "RS384", + "RS512", + "ES256", + "ES384", + "ES512" + ] + }, + "oxOpenIdConnectVersion": { + "type": "string", + "description": "OpenID Connect Version.", + "example": "openidconnect-1.0" + }, + "oxId": { + "type": "string", + "description": "URL for the Inum generator Service.", + "format": "url", + "example": "https://server.example.com/oxid/service/jans/inum" + }, + "dynamicRegistrationExpirationTime": { + "type": "integer", + "description": "Expiration time in seconds for clients created with dynamic registration, -1 means never expire.", + "example": -1 + }, + "dynamicRegistrationPersistClientAuthorizations": { + "type": "boolean", + "description": "Boolean value specifying whether to persist client authorizations." + }, + "trustedClientEnabled": { + "type": "boolean", + "description": "Boolean value specifying whether a client is trusted and no authorization is required." + }, + "skipAuthorizationForOpenIdScopeAndPairwiseId": { + "type": "boolean", + "description": "If a client has only openid scope and pairwise id, person should not have to authorize." + }, + "dynamicRegistrationScopesParamEnabled": { + "type": "boolean", + "description": "Boolean value specifying whether to enable scopes parameter in dynamic registration." + }, + "dynamicRegistrationPasswordGrantTypeEnabled": { + "type": "boolean", + "description": "Boolean value specifying whether to enable Password Grant Type during Dynamic Registration." + }, + "dynamicRegistrationAllowedPasswordGrantScopes": { + "type": "array", + "description": "List of grant scopes for dynamic registration.", + "items": { + "type": "string" + } + }, + "dynamicRegistrationCustomObjectClass": { + "type": "string", + "description": "LDAP custom object class for dynamic registration." + }, + "personCustomObjectClassList": { + "type": "array", + "description": "LDAP custom object class list for dynamic person enrolment.", + "items": { + "type": "string" + }, + "enum": [ + "gluuCustomPerson", + "gluuPerson" + ] + }, + "persistIdTokenInLdap": { + "type": "boolean", + "description": "Specifies whether to persist id_token into LDAP (otherwise saves into cache)." + }, + "persistRefreshTokenInLdap": { + "type": "boolean", + "description": "Specifies whether to persist refresh_token into LDAP (otherwise saves into cache)." + }, + "allowPostLogoutRedirectWithoutValidation": { + "type": "boolean", + "description": "Allows post logout redirect without validation for End Session Endpoint." + }, + "invalidateSessionCookiesAfterAuthorizationFlow": { + "type": "boolean", + "description": "Boolean value to specify whether to invalidate `session_id` and `consent_session_id` cookies right after successful or unsuccessful authorization." + }, + "returnClientSecretOnRead": { + "type": "boolean", + "description": "Boolean value specifying whether a client_secret is returned on client GET or PUT. False value means not to return secret." + }, + "rejectJwtWithNoneAlg": { + "type": "boolean", + "description": "Boolean value specifying whether reject JWT requested or validated with algorithm None." + }, + "expirationNotificatorEnabled": { + "type": "boolean", + "description": "Boolean value specifying whether expiration notificator is enabled (used to identify expiration for persistence that support TTL, like Couchbase)." + }, + "useNestedJwtDuringEncryption": { + "type": "boolean", + "description": "Boolean value specifying whether to use nested Jwt during encryption." + }, + "expirationNotificatorMapSizeLimit": { + "type": "integer", + "description": "The expiration notificator maximum size limit.", + "example": 100000 + }, + "expirationNotificatorIntervalInSeconds": { + "type": "integer", + "description": "The expiration notificator interval in seconds.", + "example": 600 + }, + "authenticationFiltersEnabled": { + "type": "boolean", + "description": "Boolean value specifying whether to enable user authentication filters." + }, + "clientAuthenticationFiltersEnabled": { + "type": "boolean", + "description": "Boolean value specifying whether to enable client authentication filters." + }, + "clientRegDefaultToCodeFlowWithRefresh": { + "type": "boolean", + "description": "Boolean value specifying whether to add Authorization Code Flow with Refresh grant during client registration." + }, + "authenticationFilters": { + "type": "array", + "description": "List of authentication filters.", + "items": { + "$ref": "#/components/schemas/AuthenticationFilters" + } + }, + "clientAuthenticationFilters": { + "type": "array", + "description": "List of client authentication filters.", + "items": { + "$ref": "#/components/schemas/AuthenticationFilters" + } + }, + "corsConfigurationFilters": { + "type": "array", + "description": "CORS Configuration filters.", + "items": { + "$ref": "#/components/schemas/CorsConfigurationFilter" + } + }, + "sessionIdUnusedLifetime": { + "type": "integer", + "description": "The lifetime for unused session states." + }, + "sessionIdUnauthenticatedUnusedLifetime": { + "type": "integer", + "description": "The lifetime for unused unauthenticated session states." + }, + "sessionIdEnabled": { + "type": "boolean", + "description": "Boolean value specifying whether to enable authentication by session_id." + }, + "sessionIdPersistOnPromptNone": { + "type": "boolean", + "description": "Boolean value specifying whether to persist session ID on prompt none." + }, + "sessionIdRequestParameterEnabled": { + "type": "boolean", + "description": "Boolean value specifying whether to enable session_id HTTP request parameter." + }, + "changeSessionIdOnAuthentication": { + "type": "boolean", + "description": "Boolean value specifying whether to change session_id on authentication." + }, + "sessionIdPersistInCache": { + "type": "boolean", + "description": "Boolean value specifying whether to persist session_id in cache." + }, + "sessionIdLifetime": { + "type": "integer", + "description": "The lifetime of session id in seconds. If 0 or -1 then expiration is not set. `session_id` cookie expires when browser session ends." + }, + "serverSessionIdLifetime": { + "type": "integer", + "description": "The sessionId lifetime in seconds for sessionId. By default same as sessionIdLifetime." + }, + "configurationUpdateInterval": { + "type": "integer", + "description": "The interval for configuration update in seconds." + }, + "enableClientGrantTypeUpdate": { + "type": "boolean", + "description": "Boolean value to specify if client can update Grant Type values." + }, + "dynamicGrantTypeDefault": { + "type": "array", + "description": "list of the OAuth 2.0 Grant Type values that it\\'s possible to set via client registration API..", + "items": { + "type": "string" + }, + "enum": [ + "none", + "authorization_code", + "implicit", + "password", + "client_credentials", + "refresh_token", + "urn:ietf:params:oauth:grant-type:uma-ticket", + "urn:openid:params:grant-type:ciba", + "urn:ietf:params:oauth:grant-type:device_code" + ] + }, + "cssLocation": { + "type": "string", + "description": "The location for CSS files." + }, + "jsLocation": { + "type": "string", + "description": "The location for JavaScript files." + }, + "imgLocation": { + "type": "string", + "description": "The location for image files." + }, + "metricReporterInterval": { + "type": "integer", + "description": "The interval for metric reporter in seconds." + }, + "metricReporterKeepDataDays": { + "type": "integer", + "description": "The days to keep metric reported data." + }, + "pairwiseIdType": { + "type": "string", + "description": "The pairwise ID type." + }, + "pairwiseCalculationKey": { + "type": "string", + "description": "Key to calculate algorithmic pairwise IDs." + }, + "pairwiseCalculationSalt": { + "type": "string", + "description": "Salt to calculate algorithmic pairwise IDs." + }, + "shareSubjectIdBetweenClientsWithSameSectorId": { + "type": "boolean", + "description": "Share Subject ID between clients with same Sector ID." + }, + "webKeysStorage": { + "type": "string", + "description": "Web Key Storage Type.", + "enum": [ + "keystore", + "pkcs11" + ] + }, + "dnName": { + "type": "string", + "description": "DN of certificate issuer." + }, + "keyStoreFile": { + "type": "string", + "description": "The Key Store File (JKS).", + "example": "/etc/certs/jans-auth-keys.jks" + }, + "keyStoreSecret": { + "type": "string", + "description": "The password of the Key Store." + }, + "keySelectionStrategy": { + "type": "string", + "description": "Key Selection Strategy.", + "enum": [ + "OLDER", + "NEWER", + "FIRST" + ] + }, + "checkUserPresenceOnRefreshToken": { + "type": "boolean", + "description": "Check whether user exists and is active before creating RefreshToken. Set it to true if check is needed(Default value is false - don't check.)", + "example": false, + "default": false + }, + "oxElevenTestModeToken": { + "type": "string", + "description": "oxEleven Test Mode Token." + }, + "oxElevenGenerateKeyEndpoint": { + "type": "string", + "description": "URL for the oxEleven Generate Key Endpoint.", + "example": "https://server.example.com/oxeleven/rest/oxeleven/generateKey" + }, + "oxElevenSignEndpoint": { + "type": "string", + "description": "URL for the oxEleven Sign Endpoint.", + "example": "https://server.example.com/oxeleven/rest/oxeleven/sign" + }, + "oxElevenVerifySignatureEndpoint": { + "type": "string", + "description": "URL for the oxEleven Verify Signature Endpoint.", + "example": "https://server.example.com/oxeleven/rest/oxeleven/verifySignature" + }, + "oxElevenDeleteKeyEndpoint": { + "type": "string", + "description": "URL for the oxEleven Delete Key Endpoint.", + "example": "https://server.example.com/oxeleven/rest/oxeleven/deleteKey" + }, + "introspectionAccessTokenMustHaveUmaProtectionScope": { + "type": "boolean", + "description": "Reject introspection requests if access_token in Authorization header does not have uma_protection scope." + }, + "endSessionWithAccessToken": { + "type": "boolean", + "description": "Accept access token to call end_session endpoint." + }, + "cookieDomain": { + "type": "string", + "description": "Sets cookie domain for all cookies created by OP." + }, + "enabledOAuthAuditLogging": { + "type": "boolean", + "description": "enabled OAuth Audit Logging." + }, + "jmsBrokerURISet": { + "type": "array", + "description": "JMS Broker URI Set.", + "format": "select", + "items": { + "type": "string" + } + }, + "jmsUserName": { + "type": "string", + "description": "JMS UserName." + }, + "jmsPassword": { + "type": "string", + "description": "JMS Password." + }, + "clientWhiteList": { + "type": "array", + "description": "White List for Client Redirection URIs.", + "items": { + "type": "string" + } + }, + "clientBlackList": { + "type": "array", + "description": "Black List for Client Redirection URIs.", + "items": { + "type": "string" + } + }, + "legacyIdTokenClaims": { + "type": "boolean", + "description": "Include Claims in ID Token." + }, + "customHeadersWithAuthorizationResponse": { + "type": "boolean", + "description": "Boolean value specifying whether to enable Custom Response Header parameter to return custom headers with the Authorization Response." + }, + "frontChannelLogoutSessionSupported": { + "type": "boolean", + "description": "Boolean value to specify support for front channel logout session." + }, + "loggingLevel": { + "type": "string", + "description": "Logging level for jans-auth logger.", + "enum": [ + "TRACE", + "DEBUG", + "INFO", + "WARN", + "ERROR", + "FATAL", + "OFF" + ] + }, + "loggingLayout": { + "type": "string", + "description": "Logging layout used for Jans Authorization Server loggers. - text - json" + }, + "updateUserLastLogonTime": { + "type": "boolean", + "description": "Boolean value to specify if application should update oxLastLogonTime attribute on user authentication." + }, + "updateClientAccessTime": { + "type": "boolean", + "description": "Boolean value to specify if application should update oxLastAccessTime/oxLastLogonTime attributes on client authentication." + }, + "logClientIdOnClientAuthentication": { + "type": "boolean", + "description": "Boolean value to specify if application should log the Client ID on client authentication." + }, + "logClientNameOnClientAuthentication": { + "type": "boolean", + "description": "Boolean value to specify if application should log the Client Name on client authentication." + }, + "disableJdkLogger": { + "type": "boolean", + "description": "Boolean value specifying whether to enable JDK Loggers." + }, + "authorizationRequestCustomAllowedParameters": { + "type": "array", + "description": "Authorization Request Custom Allowed Parameters.", + "items": { + "type": "string" + } + }, + "legacyDynamicRegistrationScopeParam": { + "type": "boolean", + "description": "Legacy Dynamic Registration Scopes JSON Array Param." + }, + "openidScopeBackwardCompatibility": { + "type": "boolean", + "description": "Set to false to only allow token endpoint request for openid scope with grant type equals to authorization_code, restrict access to userinfo to scope openid and only return id_token if scope contains openid." + }, + "disableU2fEndpoint": { + "type": "boolean", + "description": "Enable/Disable U2F endpoints." + }, + "useLocalCache": { + "type": "boolean", + "description": "Boolean value specifying whether to enable local in-memory cache." + }, + "fapiCompatibility": { + "type": "boolean", + "description": "Boolean value specifying whether turn on FAPI compatibility mode. If true AS behaves in more strict mode." + }, + "forceIdTokenHintPrecense": { + "type": "boolean", + "description": "Boolean value specifying whether force id_token_hint parameter presence." + }, + "forceOfflineAccessScopeToEnableRefreshToken": { + "type": "boolean", + "description": "Boolean value specifying whether force offline_access scope to enable refresh_token grant type." + }, + "errorReasonEnabled": { + "type": "boolean", + "description": "Boolean value specifying whether to return detailed reason of the error from AS.." + }, + "removeRefreshTokensForClientOnLogout": { + "type": "boolean", + "description": "Boolean value specifying whether to remove refresh tokens on logout." + }, + "skipRefreshTokenDuringRefreshing": { + "type": "boolean", + "description": "Boolean value specifying whether to skip refreshing tokens on refreshing." + }, + "refreshTokenExtendLifetimeOnRotation": { + "type": "boolean", + "description": "Boolean value specifying whether to extend refresh tokens on rotation." + }, + "consentGatheringScriptBackwardCompatibility": { + "type": "boolean", + "description": "Boolean value specifying whether turn on Consent Gathering Script backward compatibility mode. If true AS will pick up script with higher level globally. If false AS will pick up script based on client configuration." + }, + "introspectionScriptBackwardCompatibility": { + "type": "boolean", + "description": "Boolean value specifying whether switch off client\\'s introspection scripts (true value) and run all scripts that exists on server." + }, + "introspectionResponseScopesBackwardCompatibility": { + "type": "boolean", + "description": "Boolean value specifying introspection response backward compatibility mode." + }, + "softwareStatementValidationType": { + "type": "string", + "description": "Validation type used for software statement.", + "enum": [ + "none", + "jwks", + "jwks_uri", + "script" + ] + }, + "softwareStatementValidationClaimName": { + "type": "string", + "description": "Validation claim name for software statement." + }, + "authenticationProtectionConfiguration": { + "type": "object", + "description": "Authentication Brute Force Protection Configuration.", + "$ref": "#/components/schemas/AuthenticationProtectionConfiguration" + }, + "errorHandlingMethod": { + "type": "string", + "description": "A list of possible error handling methods.", + "enum": [ + "internal", + "remote" + ] + }, + "keepAuthenticatorAttributesOnAcrChange": { + "type": "boolean", + "description": "Boolean value specifying whether to keep authenticator attributes on ACR change." + }, + "deviceAuthzRequestExpiresIn": { + "type": "integer", + "description": "Expiration time given for device authorization requests." + }, + "deviceAuthzTokenPollInterval": { + "type": "integer", + "description": "Default interval returned to the client to process device token requests." + }, + "deviceAuthzResponseTypeToProcessAuthz": { + "type": "string", + "description": "Response type used to process device authz requests." + }, + "backchannelClientId": { + "type": "string", + "description": "Backchannel Client Id." + }, + "backchannelRedirectUri": { + "type": "string", + "description": "Backchannel Redirect Uri.", + "example": "https://server.example.com/oxeleven/rest/backchannel/backchannelRedirectUri" + }, + "backchannelAuthenticationEndpoint": { + "type": "string", + "description": "Backchannel Authentication Endpoint.", + "example": "https://server.example.com/oxeleven/rest/backchannel/backchannelAuthenticationEndpoint()" + }, + "backchannelDeviceRegistrationEndpoint": { + "type": "string", + "description": "Backchannel Device Registration Endpoint.", + "example": "https://server.example.com/oxeleven/rest/backchannel/backchannelDeviceRegistrationEndpoint" + }, + "backchannelTokenDeliveryModesSupported": { + "type": "array", + "description": "Backchannel Token Delivery Modes Supported.", + "items": { + "type": "string", + "enum": [ + "poll", + "ping", + "push" + ] + } + }, + "backchannelAuthenticationRequestSigningAlgValuesSupported": { + "type": "array", + "description": "Backchannel Authentication Request Signing Alg Values Supported.", + "items": { + "type": "string", + "enum": [ + "RS512", + "ES256", + "ES384", + "ES512", + "PS256", + "PS384", + "PS512", + "RS384", + "RS256" + ] + } + }, + "backchannelUserCodeParameterSupported": { + "type": "boolean", + "description": "Backchannel User Code Parameter Supported" + }, + "backchannelBindingMessagePattern": { + "type": "string", + "description": "Backchannel Binding Message Pattern." + }, + "backchannelAuthenticationResponseExpiresIn": { + "type": "integer", + "description": "Backchannel Authentication Response Expires In." + }, + "backchannelAuthenticationResponseInterval": { + "type": "integer", + "description": "Backchannel Authentication Response Interval." + }, + "backchannelLoginHintClaims": { + "type": "array", + "description": "Backchannel Login Hint Claims.", + "items": { + "type": "string" + } + }, + "cibaEndUserNotificationConfig": { + "type": "object", + "description": "CIBA End User Notification Config.", + "$ref": "#/components/schemas/CIBAEndUserNotificationConfig" + }, + "backchannelRequestsProcessorJobIntervalSec": { + "type": "integer", + "description": "Specifies the allowable elapsed time in seconds backchannel request processor executes." + }, + "backchannelRequestsProcessorJobChunkSize": { + "type": "integer", + "description": "Each backchannel request processor iteration fetches chunk of data to be processed." + }, + "cibaGrantLifeExtraTimeSec": { + "type": "integer", + "description": "Specifies the CIBA Grant life extra time in seconds." + }, + "cibaMaxExpirationTimeAllowedSec": { + "type": "integer", + "description": "Specifies the CIBA token expiration time in seconds." + }, + "discoveryCacheLifetimeInMinutes": { + "type": "integer", + "description": "Lifetime of discovery cache." + }, + "httpLoggingEnabled": { + "type": "boolean", + "description": "Enable/Disable request/response logging filter." + }, + "httpLoggingExcludePaths": { + "type": "array", + "description": "List of base URI for which request/response logging filter should not record activity.", + "items": { + "type": "string" + }, + "example": "\\\"/auth/img\\\", \\\"/auth/stylesheet\\\"" + }, + "externalLoggerConfiguration": { + "type": "string", + "description": "Path to external log4j2 logging configuration.", + "example": "/identity/logviewer/configure" + }, + "dcrSignatureValidationEnabled": { + "type": "boolean", + "description": "Boolean value enables DCR signature validation. Default is false." + }, + "dcrSignatureValidationSharedSecret": { + "type": "string", + "description": "Specifies shared secret for Dynamic Client Registration." + }, + "dcrSignatureValidationSoftwareStatementJwksURIClaim": { + "type": "string", + "description": "Specifies claim name inside software statement. Value of claim should point to JWKS URI." + }, + "dcrSignatureValidationSoftwareStatementJwksClaim": { + "type": "string", + "description": "Specifies claim name inside software statement. Value of claim should point to inlined JWKS." + }, + "dcrSignatureValidationJwks": { + "type": "string", + "description": "Specifies JWKS for all DCR's validations." + }, + "dcrSignatureValidationJwksUri": { + "type": "string", + "description": "Specifies JWKS URI for all DCR's validations." + }, + "dcrAuthorizationWithClientCredentials": { + "type": "boolean", + "description": "Boolean value indicating if DCR authorization to be performed using client credentials." + }, + "statTimerIntervalInSeconds": { + "type": "integer", + "description": "Statistical data capture time interval." + }, + "statWebServiceIntervalLimitInSeconds": { + "type": "integer", + "description": "Statistical data capture time interval limit." + }, + "keyAlgsAllowedForGeneration": { + "type": "array", + "description": "List of algorithm allowed to be used for key generation.", + "items": { + "type": "string" + }, + "example": "\\\"RS256\\\", \\\"RS512\\\", \\\"ES384\\\", \\\"PS256\\\"" + }, + "discoveryAllowedKeys": { + "type": "array", + "description": "List of configuration response claim allowed to be displayed in discovery endpoint.", + "items": { + "type": "string" + }, + "example": "authorization_endpoint, token_endpoint, jwks_uri, scopes_supported, response_types_supported, response_modes_supported, etc.." + }, + "allowIdTokenWithoutImplicitGrantTypes": { + "type": "boolean", + "description": "Specifies if a token without implicit grant types is allowed." + }, + "keySignWithSameKeyButDiffAlg": { + "type": "boolean", + "description": "Specifies if signing to be done with same key but apply different algorithms." + }, + "enabledComponents": { + "type": "array", + "description": "List of auth components enabled", + "items": { + "type": "string" + }, + "example": "HEALTH_CHECK, USERINFO, CLIENTINFO, ID_GENERATION, REGISTRATION, INTROSPECTION, etc.." + }, + "staticKid": { + "type": "string", + "description": "Specifies static Kid" + }, + "redirectUrisRegexEnabled": { + "type": "boolean", + "description": "Enable/Disable redirect uris validation using regular expression." + }, + "useHighestLevelScriptIfAcrScriptNotFound": { + "type": "boolean", + "description": "Enable/Disable usage of highest level script in case ACR script does not exist." + }, + "agamaConfiguration": { + "type": "object", + "desciption": "Engine Config which offers an alternative way to build authentication flows in Janssen server", + "$ref": "#/components/schemas/EngineConfig" + } + } + }, + "GluuAttribute": { + "title": "GluuAttribute", + "description": "Attribute.", + "type": "object", + "required": [ + "name", + "description", + "displayName", + "dataType", + "status", + "viewType", + "editType" + ], + "properties": { + "dn": { + "type": "string" + }, + "inum": { + "description": "XRI i-number. Identifier to uniquely identify the attribute.", + "type": "string" + }, + "name": { + "type": "string", + "description": "Name of the attribute.", + "example": "name, displayName, birthdate, email" + }, + "displayName": { + "type": "string" + }, + "description": { + "type": "string", + "description": "User friendly descriptive detail of attribute." + }, + "dataType": { + "type": "string", + "description": "Data Type of attribute.", + "enum": [ + "STRING", + "NUMERIC", + "BOOLEAN", + "BINARY", + "CERTIFICATE", + "DATE", + "JSON" + ] + }, + "status": { + "type": "string", + "description": "Attrubute status", + "enum": [ + "ACTIVE", + "INACTIVE", + "EXPIRED", + "REGISTER" + ] + }, + "lifetime": { + "type": "string" + }, + "sourceAttribute": { + "type": "string" + }, + "salt": { + "type": "string" + }, + "nameIdType": { + "type": "string" + }, + "origin": { + "type": "string" + }, + "editType": { + "type": "array", + "description": "GluuUserRole", + "items": { + "type": "string", + "enum": [ + "ADMIN", + "OWNER", + "MANAGER", + "USER", + "WHITEPAGES" + ] + } + }, + "viewType": { + "type": "array", + "description": "GluuUserRole", + "items": { + "type": "string", + "enum": [ + "ADMIN", + "OWNER", + "MANAGER", + "USER", + "WHITEPAGES" + ] + } + }, + "usageType": { + "type": "array", + "description": "GluuAttributeUsageType", + "items": { + "type": "string", + "enum": [ + "OPENID" + ] + } + }, + "claimName": { + "type": "string" + }, + "seeAlso": { + "type": "string" + }, + "saml1Uri": { + "type": "string" + }, + "saml2Uri": { + "type": "string" + }, + "urn": { + "type": "string" + }, + "scimCustomAttr": { + "type": "boolean", + "description": "Boolean value indicating if the attribute is a SCIM custom attribute" + }, + "oxMultiValuedAttribute": { + "type": "boolean", + "description": "Boolean value indicating if the attribute can hold multiple value." + }, + "attributeValidation": { + "type": "object", + "description": "Details of validations to be applied on the attribute", + "properties": { + "regexp": { + "type": "string", + "description": "Reguar expression to be used to validate the dataType." + }, + "minLength": { + "type": "integer" + }, + "maxLength": { + "type": "integer" + } + } + }, + "tooltip": { + "type": "string" + }, + "jansHideOnDiscovery": { + "type": "boolean", + "description": "Boolean value indicating if the attribute should be shown on that discovery page." + } + } + }, + "PatchRequest": { + "description": "A JSONPatch document as defined by RFC 6902", + "required": [ + "op", + "path" + ], + "properties": { + "op": { + "type": "string", + "description": "The operation to be performed", + "enum": [ + "add", + "remove", + "replace", + "move", + "copy", + "test" + ] + }, + "path": { + "type": "string", + "description": "A JSON-Pointer", + "example": "/client/customattribute/[0]" + }, + "value": { + "type": "object", + "description": "The value to be used within the operations." + } + } + }, + "Scope": { + "title": "Scope", + "description": "Auth Scope.", + "type": "object", + "required": [ + "id", + "scopeType" + ], + "properties": { + "dn": { + "type": "string" + }, + "inum": { + "description": "Unique id identifying the .", + "type": "string" + }, + "displayName": { + "description": "A human-readable name of the scope.", + "type": "string" + }, + "id": { + "description": "The base64url encoded id.", + "type": "string" + }, + "iconUrl": { + "description": "A URL for a graphic icon representing the scope. The referenced icon MAY be used by the authorization server in any user interface it presents to the resource owner.", + "type": "string" + }, + "description": { + "description": "A human-readable string describing the scope.", + "type": "string" + }, + "scopeType": { + "description": "The scopes type associated with Access Tokens determine what resources will.", + "type": "string", + "enum": [ + "openid", + "dynamic", + "uma", + "spontaneous", + "oauth" + ] + }, + "claims": { + "description": "Claim attributes associated with the scope.", + "type": "array", + "items": { + "type": "string" + } + }, + "defaultScope": { + "description": "Boolean value to specify default scope.", + "type": "boolean" + }, + "groupClaims": { + "description": "Specifies if the scope is group claims.", + "type": "boolean" + }, + "dynamicScopeScripts": { + "description": "Dynamic Scope Scripts associated with the scope.", + "type": "array", + "items": { + "type": "string" + } + }, + "umaAuthorizationPolicies": { + "description": "Policies associated with scopes.", + "type": "array", + "items": { + "type": "string" + } + }, + "attributes": { + "type": "object", + "description": "ScopeAttributes", + "properties": { + "spontaneousClientId": { + "type": "string" + }, + "spontaneousClientScopes": { + "type": "array", + "items": { + "type": "string" + } + }, + "showInConfigurationEndpoint": { + "type": "boolean" + } + } + }, + "umaType": { + "description": "Specifies if the scope is of type UMA.", + "type": "boolean", + "default": false + }, + "deletable": { + "description": "Specifies if the scope can be deleted.", + "type": "boolean", + "default": false + }, + "expirationDate": { + "description": "Expiry date of the Scope.", + "type": "string", + "format": "date" + } + } + }, + "CustomAttribute": { + "title": "CustomAttribute", + "description": "Attribute.", + "type": "object", + "required": [ + "name", + "multiValued", + "values" + ], + "properties": { + "name": { + "type": "string", + "description": "Name of the attribute.", + "example": "name, displayName, birthdate, email" + }, + "multiValued": { + "type": "boolean", + "description": "Indicates if the attribute can hold multiple values." + }, + "values": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "ClientAttributes": { + "title": "ClientAttributes", + "description": "Attribute.", + "type": "object", + "properties": { + "tlsClientAuthSubjectDn": { + "description": "String representation of the expected subject distinguished name of the certificate, which the OAuth client will use in mutual TLS authentication.", + "type": "string" + }, + "runIntrospectionScriptBeforeAccessTokenAsJwtCreationAndIncludeClaims": { + "description": "boolean property which indicates whether to run introspection script and then include claims from result into access_token as JWT.", + "type": "boolean" + }, + "keepClientAuthorizationAfterExpiration": { + "description": "boolean property which indicates whether to keep client authorization after expiration.", + "type": "boolean" + }, + "allowSpontaneousScopes": { + "description": "boolean, whether to allow spontaneous scopes for client.", + "type": "boolean" + }, + "spontaneousScopes": { + "description": "List of spontaneous scope regular expression.", + "type": "array", + "items": { + "type": "string" + } + }, + "spontaneousScopeScriptDns": { + "description": "List of spontaneous scope scripts.", + "type": "array", + "items": { + "type": "string" + } + }, + "updateTokenScriptDns": { + "description": "List of update token scripts.", + "type": "array", + "items": { + "type": "string" + } + }, + "backchannelLogoutUri": { + "description": "List of RP URL that will cause the RP to log itself out when sent a Logout Token by the OP.", + "type": "array", + "items": { + "type": "string" + } + }, + "backchannelLogoutSessionRequired": { + "description": "Boolean value specifying whether the RP requires that a sid (session ID) Claim be included in the Logout Token to identify the RP session with the OP when true. Default value is false.", + "type": "boolean" + }, + "additionalAudience": { + "description": "List of additional client audience.", + "type": "array", + "items": { + "type": "string" + } + }, + "postAuthnScripts": { + "description": "List of post authentication scripts.", + "type": "array", + "items": { + "type": "string" + } + }, + "consentGatheringScripts": { + "description": "List of consent gathering scripts.", + "type": "array", + "items": { + "type": "string" + } + }, + "introspectionScripts": { + "description": "List of introspection scripts.", + "type": "array", + "items": { + "type": "string" + } + }, + "rptClaimsScripts": { + "description": "List of Requesting Party Token (RPT) claims scripts.", + "type": "array", + "items": { + "type": "string" + } + }, + "ropcScripts": { + "description": "List of Resource Owner Password Credentials (ROPC) scripts.", + "type": "array", + "items": { + "type": "string" + } + }, + "parLifetime": { + "description": "represents the lifetime of Pushed Authorisation Request (PAR).", + "type": "integer", + "format": "int64" + }, + "requirePar": { + "description": "boolean value to indicate of Pushed Authorisation Request(PAR)is required.", + "type": "boolean" + }, + "authorizationSignedResponseAlg": { + "description": "JWS alg algorithm JWA required for signing authorization responses.", + "type": "string" + }, + "authorizationEncryptedResponseAlg": { + "description": "JWE alg algorithm JWA required for encrypting authorization responses.", + "type": "string" + }, + "authorizationEncryptedResponseEnc": { + "description": "JWE enc algorithm JWA required for encrypting auhtorization responses.", + "type": "string" + }, + "publicSubjectIdentifierAttribute": { + "description": "custom subject identifier attribute.", + "type": "string" + }, + "redirectUrisRegex": { + "description": "If set, redirectUri must match to this regexp", + "type": "string" + }, + "authorizedAcrValues": { + "description": "List of thentication Context Class Reference (ACR) that must exist.", + "type": "array", + "items": { + "type": "string" + } + }, + "defaultPromptLogin": { + "description": "sets prompt=login to the authorization request, which causes the authorization server to force the user to sign in again before it will show the authorization prompt.", + "type": "boolean" + } + } + }, + "Client": { + "title": "Client object", + "description": "Client.", + "type": "object", + "required": [ + "redirectUris" + ], + "properties": { + "dn": { + "type": "string" + }, + "inum": { + "description": "XRI i-number. Client Identifier to uniquely identify the client.", + "type": "string" + }, + "displayName": { + "type": "string", + "description": "Name of the user suitable for display to end-users" + }, + "clientSecret": { + "type": "string", + "description": "The client secret. The client MAY omit the parameter if the client secret is an empty string." + }, + "frontChannelLogoutUri": { + "type": "string" + }, + "frontChannelLogoutSessionRequired": { + "type": "boolean" + }, + "registrationAccessToken": { + "type": "string" + }, + "clientIdIssuedAt": { + "type": "string", + "format": "date-time" + }, + "clientSecretExpiresAt": { + "type": "string", + "format": "date-time" + }, + "redirectUris": { + "description": "Redirection URI values used by the Client. One of these registered Redirection URI values must exactly match the redirect_uri parameter value used in each Authorization Request", + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "https://client.example.org/cb" + ] + }, + "claimRedirectUris": { + "description": "Array of The Claims Redirect URIs to which the client wishes the authorization server to direct the requesting party's user agent after completing its interaction.", + "type": "array", + "items": { + "type": "string" + } + }, + "responseTypes": { + "description": "A list of the OAuth 2.0 response_type values that the Client is declaring that it will restrict itself to using. If omitted, the default is that the Client will use only the code Response Type. Allowed values are code, token, id_token.", + "type": "array", + "items": { + "type": "string", + "enum": [ + "code", + "token", + "id_token" + ] + } + }, + "grantTypes": { + "description": "A list of the OAuth 2.0 Grant Types that the Client is declaring that it will restrict itself to using.", + "type": "array", + "items": { + "type": "string", + "enum": [ + "authorization_code", + "implicit", + "password", + "client_credentials", + "refresh_token", + "urn:ietf:params:oauth:grant-type:uma-ticket", + "urn:openid:params:grant-type:ciba", + "urn:ietf:params:oauth:grant-type:device_code" + ] + } + }, + "applicationType": { + "description": "Kind of the application. The default, if omitted, is web. The defined values are native or web. Web Clients using the OAuth Implicit Grant Type must only register URLs using the HTTPS scheme as redirect_uris, they must not use localhost as the hostname. Native Clients must only register redirect_uris using custom URI schemes or URLs using the http scheme with localhost as the hostname.", + "type": "string", + "enum": [ + "web", + "native" + ] + }, + "contacts": { + "description": "e-mail addresses of people responsible for this Client.", + "type": "array", + "items": { + "type": "string" + } + }, + "idTokenTokenBindingCnf": { + "description": "Specifies the JWT Confirmation Method member name (e.g. tbh) that the Relying Party expects when receiving Token Bound ID Tokens. The presence of this parameter indicates that the Relying Party supports Token Binding of ID Tokens. If omitted, the default is that the Relying Party does not support Token Binding of ID Tokens.", + "type": "string" + }, + "logoUri": { + "description": "URL that references a logo for the Client application.", + "type": "string" + }, + "clientUri": { + "description": "URL of the home page of the Client. The value of this field must point to a valid Web page.", + "type": "string" + }, + "policyUri": { + "description": "URL that the Relying Party Client provides to the End-User to read about the how the profile data will be used.", + "type": "string" + }, + "tosUri": { + "description": "URL that the Relying Party Client provides to the End-User to read about the Relying Party's terms of service.", + "type": "string" + }, + "jwksUri": { + "description": "URL for the Client's JSON Web Key Set (JWK) document containing key(s) that are used for signing requests to the OP. The JWK Set may also contain the Client's encryption keys(s) that are used by the OP to encrypt the responses to the Client. When both signing and encryption keys are made available, a use (Key Use) parameter value is required for all keys in the document to indicate each key's intended usage.", + "type": "string" + }, + "jwks": { + "description": "List of JSON Web Key (JWK) - A JSON object that represents a cryptographic key. The members of the object represent properties of the key, including its value.", + "type": "string", + "example": "{ \"keys\" : [ { \"e\" : \"AQAB\", \"n\" : \"gmlDX_mgMcHX..\" ] }" + }, + "sectorIdentifierUri": { + "description": "URL using the https scheme to be used in calculating Pseudonymous Identifiers by the OP.", + "type": "string" + }, + "subjectType": { + "description": "Subject type requested for the Client ID. Valid types include pairwise and public.", + "type": "string", + "enum": [ + "pairwise", + "public" + ] + }, + "idTokenSignedResponseAlg": { + "description": "JWS alg algorithm (JWA) required for signing the ID Token issued to this Client.", + "type": "string", + "enum": [ + "HS256", + "HS384", + "HS512", + "RS256", + "RS384", + "RS512", + "ES256", + "ES384", + "ES512", + "PS256", + "PS384", + "PS512" + ] + }, + "idTokenEncryptedResponseAlg": { + "description": "JWE alg algorithm (JWA) required for encrypting the ID Token issued to this Client.", + "type": "string", + "enum": [ + "RSA1_5", + "RSA-OAEP", + "A128KW", + "A256KW" + ] + }, + "idTokenEncryptedResponseEnc": { + "description": "JWE enc algorithm (JWA) required for encrypting the ID Token issued to this Client.", + "type": "string", + "enum": [ + "A128CBC+HS256", + "A256CBC+HS512", + "A128GCM", + "A256GCM" + ] + }, + "userInfoSignedResponseAlg": { + "description": "JWS alg algorithm (JWA) required for signing UserInfo Responses.", + "type": "string", + "enum": [ + "HS256", + "HS384", + "HS512", + "RS256", + "RS384", + "RS512", + "ES256", + "ES384", + "ES512", + "PS256", + "PS384", + "PS512" + ] + }, + "userInfoEncryptedResponseAlg": { + "description": "JWE alg algorithm (JWA) required for encrypting UserInfo Responses.", + "type": "string", + "enum": [ + "RSA1_5", + "RSA-OAEP", + "A128KW", + "A256KW" + ] + }, + "userInfoEncryptedResponseEnc": { + "description": "JWE enc algorithm (JWA) required for encrypting UserInfo Responses.", + "type": "string", + "enum": [ + "A128CBC+HS256", + "A256CBC+HS512", + "A128GCM", + "A256GCM" + ] + }, + "requestObjectSigningAlg": { + "description": "JWS alg algorithm (JWA) that must be used for signing Request Objects sent to the OP.", + "type": "string", + "enum": [ + "HS256", + "HS384", + "HS512", + "RS256", + "RS384", + "RS512", + "ES256", + "ES384", + "ES512", + "PS256", + "PS384", + "PS512" + ] + }, + "requestObjectEncryptionAlg": { + "description": "JWE alg algorithm (JWA) the RP is declaring that it may use for encrypting Request Objects sent to the OP.", + "type": "string", + "enum": [ + "RSA1_5", + "RSA-OAEP", + "A128KW", + "A256KW" + ] + }, + "requestObjectEncryptionEnc": { + "description": "JWE enc algorithm (JWA) the RP is declaring that it may use for encrypting Request Objects sent to the OP.", + "type": "string", + "enum": [ + "A128CBC+HS256", + "A256CBC+HS512", + "A128GCM", + "A256GCM" + ] + }, + "tokenEndpointAuthMethod": { + "description": "Requested Client Authentication method for the Token Endpoint.", + "type": "string", + "enum": [ + "client_secret_basic", + "client_secret_post", + "client_secret_jwt", + "private_key_jwt", + "tls_client_auth", + "none" + ] + }, + "tokenEndpointAuthSigningAlg": { + "description": "JWS alg algorithm (JWA) that must be used for signing the JWT used to authenticate the Client at the Token Endpoint for the private_key_jwt and client_secret_jwt authentication methods.", + "type": "string", + "enum": [ + "HS256", + "HS384", + "HS512", + "RS256", + "RS384", + "RS512", + "ES256", + "ES384", + "ES512", + "PS256", + "PS384", + "PS512" + ] + }, + "defaultMaxAge": { + "description": "Specifies the Default Maximum Authentication Age.", + "type": "integer", + "format": "int32", + "example": 1000000 + }, + "requireAuthTime": { + "description": "Boolean value specifying whether the auth_time Claim in the ID Token is required. It is required when the value is true.", + "type": "boolean" + }, + "defaultAcrValues": { + "description": "Array of default requested Authentication Context Class Reference values that the Authorization Server must use for processing requests from the Client.", + "type": "array", + "items": { + "type": "string" + } + }, + "initiateLoginUri": { + "description": "Specifies the URI using the https scheme that the authorization server can call to initiate a login at the client.", + "type": "string" + }, + "postLogoutRedirectUris": { + "description": "Provide the URLs supplied by the RP to request that the user be redirected to this location after a logout has been performed.", + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "https://client.example.org/logout/page1", + "https://client.example.org/logout/page2", + "https://client.example.org/logout/page3" + ] + }, + "requestUris": { + "description": "Provide a list of requests_uri values that are pre-registered by the Client for use at the Authorization Server.", + "type": "array", + "items": { + "type": "string" + } + }, + "scopes": { + "description": "Provide list of scopes granted to the client (scope dn or scope id).", + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "read write dolphin" + ] + }, + "claims": { + "description": "Provide list of claims granted to the client.", + "type": "array", + "items": { + "type": "string", + "description": "String containing a space-separated list of claims that can be requested individually." + } + }, + "trustedClient": { + "description": "Attribute which corresponds to the \"Pre-Authorization\" property. Default value is false.", + "type": "boolean", + "default": false + }, + "lastAccessTime": { + "description": "Integer timestamp, measured in the number of seconds since January 1 1970 UTC, indicating last access time.", + "type": "string", + "format": "date-time" + }, + "lastLogonTime": { + "description": "Integer timestamp, measured in the number of seconds since January 1 1970 UTC, indicating last login time.", + "type": "string", + "format": "date-time" + }, + "persistClientAuthorizations": { + "description": "Specifies if the client authorization details are to be persisted. Default value is true.", + "type": "boolean" + }, + "includeClaimsInIdToken": { + "description": "If true then claims are included in token id, default value is false.", + "type": "boolean", + "default": false + }, + "refreshTokenLifetime": { + "description": "Specifies the Client-specific refresh token expiration.", + "type": "integer", + "format": "int32", + "example": 100000000 + }, + "accessTokenLifetime": { + "description": "Specifies the Client-specific access token expiration.", + "type": "integer", + "format": "int32", + "example": 100000000 + }, + "customAttributes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomAttribute" + } + }, + "customObjectClasses": { + "type": "array", + "items": { + "type": "string" + } + }, + "rptAsJwt": { + "description": "Specifies whether RPT should be return as signed JWT.", + "type": "boolean" + }, + "accessTokenAsJwt": { + "description": "Specifies whether access token as signed JWT.", + "type": "boolean" + }, + "accessTokenSigningAlg": { + "description": "Specifies signing algorithm that has to be used during JWT signing. If it's not specified, then the default OP signing algorithm will be used.", + "type": "string", + "enum": [ + "HS256", + "HS384", + "HS512", + "RS256", + "RS384", + "RS512", + "ES256", + "ES384", + "ES512", + "PS256", + "PS384", + "PS512" + ] + }, + "disabled": { + "description": "Specifies whether client is disabled.", + "type": "boolean", + "default": false + }, + "authorizedOrigins": { + "description": "Specifies authorized JavaScript origins.", + "type": "array", + "items": { + "type": "string" + } + }, + "softwareId": { + "description": "Specifies a unique identifier string (UUID) assigned by the client developer or software publisher used by registration endpoints to identify the client software to be dynamically registered.", + "type": "string", + "example": "4NRB1-0XZABZI9E6-5SM3R" + }, + "softwareVersion": { + "description": "Specifies a version identifier string for the client software identified by 'software_id'. The value of the 'software_version' should change on any update to the client software identified by the same 'software_id'.", + "type": "string", + "example": "2.1" + }, + "softwareStatement": { + "description": "Specifies a software statement containing client metadata values about the client software as claims. This is a string value containing the entire signed JWT.", + "type": "string" + }, + "attributes": { + "type": "object", + "$ref": "#/components/schemas/ClientAttributes" + }, + "backchannelTokenDeliveryMode": { + "description": "specifies how backchannel token will be delivered.", + "type": "string", + "enum": [ + "poll", + "ping", + "push" + ] + }, + "backchannelClientNotificationEndpoint": { + "description": "Client Initiated Backchannel Authentication (CIBA) enables a Client to initiate the authentication of an end-user by means of out-of-band mechanisms. Upon receipt of the notification, the Client makes a request to the token endpoint to obtain the tokens.", + "type": "string" + }, + "backchannelAuthenticationRequestSigningAlg": { + "description": "The JWS algorithm alg value that the Client will use for signing authentication request, as described in Section 7.1.1. of OAuth 2.0 [RFC6749]. When omitted, the Client will not send signed authentication requests.", + "type": "string", + "enum": [ + "RS256", + "RS384", + "RS512", + "ES256", + "ES384", + "ES512", + "PS256", + "PS384", + "PS512" + ] + }, + "backchannelUserCodeParameter": { + "description": "Boolean value specifying whether the Client supports the user_code parameter. If omitted, the default value is false.", + "type": "boolean" + }, + "expirationDate": { + "description": "Integer timestamp, measured in the number of seconds since January 1 1970 UTC, indicating when this permission will expire.", + "type": "string", + "format": "date-time" + }, + "deletable": { + "description": "Specifies whether client is deletable.", + "type": "boolean", + "default": false + }, + "jansId": { + "description": "Attribute Scope Id.", + "type": "string" + }, + "description": { + "description": "Description of the client.", + "type": "string" + } + } + }, + "UmaResource": { + "title": "UMAResource object", + "description": "UMAResource", + "type": "object", + "required": [ + "id", + "type", + "iconUri" + ], + "properties": { + "dn": { + "type": "string" + }, + "inum": { + "description": "XRI i-number. Client Identifier to uniquely identify the UMAResource.", + "type": "string" + }, + "id": { + "description": "Resource id.", + "type": "string" + }, + "name": { + "description": "A human-readable name of the scope.", + "type": "string" + }, + "iconUri": { + "description": "A URL for a graphic icon representing the resource.", + "type": "string" + }, + "scopes": { + "description": "Applicable resource scopes.", + "type": "array", + "items": { + "type": "string" + } + }, + "scopeExpression": { + "description": "Resource scope expression.", + "type": "string" + }, + "clients": { + "description": "List of client assosiated with the resource.", + "type": "array", + "items": { + "type": "string" + } + }, + "resources": { + "description": "List of assosiated resource.", + "type": "array", + "items": { + "type": "string" + } + }, + "rev": { + "description": "Resource revision.", + "type": "string" + }, + "creator": { + "description": "Resource creator or owner.", + "type": "string" + }, + "description": { + "description": "Resource description.", + "type": "string" + }, + "type": { + "description": "Resource type.", + "type": "string" + }, + "creationDate": { + "description": "Integer timestamp, measured in the number of seconds since January 1 1970 UTC, indicating when this resource will created.", + "type": "string", + "format": "date-time" + }, + "expirationDate": { + "description": "Integer timestamp, measured in the number of seconds since January 1 1970 UTC, indicating when this resource will expire.", + "type": "string", + "format": "date-time" + }, + "deletable": { + "description": "Specifies whether client is deletable.", + "type": "boolean", + "default": false + } + } + }, + "SectorIdentifier": { + "type": "object", + "description": "Sector Identifier Details.", + "required": [ + "id" + ], + "properties": { + "id": { + "description": "XRI i-number. Sector Identifier to uniquely identify the sector.", + "type": "string" + }, + "description": { + "description": "A human-readable string describing the sector.", + "type": "string" + }, + "redirectUris": { + "description": "Redirection URI values used by the Client. One of these registered Redirection URI values must exactly match the redirect_uri parameter value used in each Authorization Request", + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "https://client.example.org/cb" + ] + }, + "clientIds": { + "description": "List of OAuth 2.0 Client Identifier valid at the Authorization Server.", + "type": "array", + "items": { + "type": "string", + "example": "1402.0ab17362-36cc-4ac8-9c73-20239de64364 API Requesting Party Client." + } + } + } + }, + "CacheConfiguration": { + "type": "object", + "description": "Cache Configuration Details.", + "properties": { + "cacheProviderType": { + "description": "The cache Provider Type.", + "type": "string", + "enum": [ + "IN_MEMORY", + "MEMCACHED", + "REDIS", + "NATIVE_PERSISTENCE" + ] + }, + "memcachedConfiguration": { + "type": "object", + "$ref": "#/components/schemas/MemcachedConfiguration" + }, + "redisConfiguration": { + "type": "object", + "$ref": "#/components/schemas/RedisConfiguration" + }, + "inMemoryConfiguration": { + "type": "object", + "$ref": "#/components/schemas/InMemoryConfiguration" + }, + "nativePersistenceConfiguration": { + "type": "object", + "$ref": "#/components/schemas/NativePersistenceConfiguration" + } + } + }, + "MemcachedConfiguration": { + "description": "Memcached cache configuration.", + "type": "object", + "properties": { + "servers": { + "type": "string", + "description": "Server details separated by spaces.", + "format": "url", + "minLength": 1 + }, + "maxOperationQueueLength": { + "type": "integer", + "description": "Maximum operation Queue Length." + }, + "bufferSize": { + "type": "integer", + "description": "Buffer Size." + }, + "defaultPutExpiration": { + "type": "integer", + "description": "Expiration timeout value." + }, + "connectionFactoryType": { + "type": "string", + "description": "The MemcachedConnectionFactoryType Type.", + "enum": [ + "DEFAULT", + "BINARY" + ] + } + } + }, + "RedisConfiguration": { + "type": "object", + "description": "Cache Configuration", + "properties": { + "redisProviderType": { + "description": "Type of connection.", + "type": "string", + "enum": [ + "STANDALONE", + "CLUSTER", + "SHARDED", + "SENTINEL" + ] + }, + "servers": { + "description": "server details separated by comma e.g. 'server1:8080server2:8081'.", + "type": "string", + "title": "servers", + "format": "url" + }, + "password": { + "description": "Redis password.", + "type": "string" + }, + "defaultPutExpiration": { + "description": "defaultPutExpiration timeout value.", + "type": "integer" + }, + "sentinelMasterGroupName": { + "description": "Sentinel Master Group Name (required if SENTINEL type of connection is selected).", + "type": "string" + }, + "useSSL": { + "description": "Enable SSL communication between Gluu Server and Redis cache.", + "type": "boolean" + }, + "sslTrustStoreFilePath": { + "description": "Directory Path to Trust Store.", + "type": "string", + "format": "url" + }, + "maxIdleConnections": { + "description": "The cap on the number of \\idle\\ instances in the pool. If max idle is set too low on heavily loaded systems it is possible you will see objects being destroyed and almost immediately new objects being created. This is a result of the active threads momentarily returning objects faster than they are requesting them causing the number of idle objects to rise above max idle. The best value for max idle for heavily loaded system will vary but the default is a good starting point.", + "type": "integer" + }, + "maxTotalConnections": { + "description": "The number of maximum connection instances in the pool.", + "type": "integer" + }, + "connectionTimeout": { + "description": "Connection time out.", + "type": "integer" + }, + "soTimeout": { + "description": "With this option set to a non-zero timeout a read() call on the InputStream associated with this Socket will block for only this amount of time. If the timeout expires a java.net.SocketTimeoutException is raised though the Socket is still valid. The option must be enabled prior to entering the blocking operation to have effect. The timeout must be > 0. A timeout of zero is interpreted as an infinite timeout.", + "type": "integer" + }, + "maxRetryAttempts": { + "description": "Maximum retry attempts in case of failure.", + "type": "integer" + } + } + }, + "InMemoryConfiguration": { + "type": "object", + "description": "Cache configuration.", + "properties": { + "defaultPutExpiration": { + "description": "defaultPutExpiration timeout value.", + "type": "integer" + } + } + }, + "NativePersistenceConfiguration": { + "type": "object", + "description": "Cache configuration.", + "properties": { + "defaultPutExpiration": { + "description": "defaultPutExpiration timeout value.", + "type": "integer" + }, + "defaultCleanupBatchSize": { + "description": "defaultCleanupBatchSize page size.", + "type": "integer" + }, + "deleteExpiredOnGetRequest": { + "type": "boolean" + } + } + }, + "SmtpConfiguration": { + "type": "object", + "description": "SMTP configuration.", + "properties": { + "host": { + "description": "Hostname of the SMTP server.", + "type": "string", + "format": "url" + }, + "port": { + "description": "Port number of the SMTP server.", + "type": "integer", + "format": "int32", + "multipleOf": 1 + }, + "requires_ssl": { + "description": "Boolean value with default value false. If true, SSL will be enabled.", + "type": "boolean" + }, + "trust_host": { + "type": "boolean", + "description": "Boolean value with default value false." + }, + "from_name": { + "description": "Name of the sender.", + "type": "string" + }, + "from_email_address": { + "description": "Email Address of the Sender.", + "type": "string" + }, + "requires_authentication": { + "description": "Boolean value with default value false. It true it will enable sender authentication.", + "type": "boolean" + }, + "user_name": { + "description": "Username of the SMTP.", + "type": "string" + }, + "password": { + "description": "Password for the SMTP.", + "type": "string" + } + } + }, + "LoggingConfiguration": { + "type": "object", + "description": "Log configuration.", + "properties": { + "loggingLevel": { + "type": "string", + "description": "Logging level for Jans Authorization Server logger.", + "enum": [ + "TRACE", + "DEBUG", + "INFO", + "WARN", + "ERROR", + "FATAL", + "OFF" + ] + }, + "loggingLayout": { + "type": "string", + "description": "Logging layout used for Jans Authorization Server loggers.", + "enum": [ + "text", + "json" + ] + }, + "httpLoggingEnabled": { + "description": "To enable http request/response logging.", + "type": "boolean" + }, + "disableJdkLogger": { + "description": "To enable/disable Jdk logging.", + "type": "boolean" + }, + "enabledOAuthAuditLogging": { + "description": "To enable/disable OAuth audit logging.", + "type": "boolean" + }, + "externalLoggerConfiguration": { + "description": "Path to external log4j2 configuration file.", + "type": "string" + }, + "httpLoggingExcludePaths": { + "description": "List of paths to exclude from logger.", + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "/auth/img", + "/auth/stylesheet" + ] + } + } + }, + "WebKeysConfiguration": { + "type": "object", + "description": "JSON Web Key Set (JWKS) - A JSON object that represents a set of JWKs. The JSON object MUST have a keys member, which is an array of JWKs.", + "required": [ + "keys" + ], + "properties": { + "keys": { + "type": "array", + "description": "List of JSON Web Key (JWK) - A JSON object that represents a cryptographic key. The members of the object represent properties of the key, including its value.", + "items": { + "$ref": "#/components/schemas/JsonWebKey" + } + } + } + }, + "AuthenticationMethod": { + "type": "object", + "description": "Authentication Method Configuration", + "properties": { + "defaultAcr": { + "type": "string", + "description": "This field controls the default authentication mechanism that is presented to users from all applications that leverage Janssen Server for authentication." + } + } + }, + "JansFido2DynConfiguration": { + "type": "object", + "description": "Jans Fido2 dynamic configuration properties.", + "properties": { + "issuer": { + "type": "string", + "description": "URL using the https scheme for Issuer identifier.", + "example": "https://server.example.com/" + }, + "baseEndpoint": { + "type": "string", + "description": "The base URL for Fido2 endpoints.", + "example": "https://server.example.com/fido2/restv1" + }, + "cleanServiceInterval": { + "type": "integer", + "description": "Time interval for the Clean Service in seconds." + }, + "cleanServiceBatchChunkSize": { + "type": "integer", + "description": "Each clean up iteration fetches chunk of expired data per base dn and removes it from storage." + }, + "useLocalCache": { + "description": "Boolean value to indicate if Local Cache is to be used.", + "type": "boolean" + }, + "disableJdkLogger": { + "type": "boolean", + "description": "Boolean value specifying whether to enable JDK Loggers." + }, + "loggingLevel": { + "type": "string", + "description": "Logging level for Fido2 logger." + }, + "loggingLayout": { + "type": "string", + "description": "Logging layout used for Fido2." + }, + "externalLoggerConfiguration": { + "type": "string", + "description": "Path to external Fido2 logging configuration." + }, + "metricReporterInterval": { + "type": "integer", + "description": "The interval for metric reporter in seconds." + }, + "metricReporterKeepDataDays": { + "type": "integer", + "description": "The days to keep report data." + }, + "personCustomObjectClassList": { + "type": "array", + "description": "Custom object class list for dynamic person enrolment.", + "items": { + "type": "string" + } + }, + "fido2Configuration": { + "description": "Fido2Configuration.", + "$ref": "#/components/schemas/Fido2Configuration" + } + } + }, + "Fido2RegistrationEntry": { + "type": "object", + "description": "Fido2 registration entry", + "properties": { + "publicKeyId": { + "description": "Public key id", + "type": "string" + }, + "displayName": { + "description": "Dislay name", + "type": "string" + }, + "counter": { + "description": "counter", + "type": "integer" + }, + "deviceNotificationConf": { + "description": "Device notification configuration", + "type": "string" + }, + "challangeHash": { + "description": "Challange hash", + "type": "string" + }, + "registrationData": { + "description": "Fido2 registration data.", + "$ref": "#/components/schemas/Fido2RegistrationData" + }, + "registrationStatus": { + "description": "registration status", + "type": "string", + "enum": [ + "pending", + "registered", + "compromised" + ] + } + } + }, + "Fido2RegistrationData": { + "type": "object", + "description": "Fido2 registration data.", + "properties": { + "username": { + "description": "Username", + "type": "string" + }, + "domain": { + "description": "Domain", + "type": "string" + }, + "userId": { + "description": "user id", + "type": "string" + }, + "challenge": { + "description": "challenge", + "type": "string" + }, + "attenstationRequest": { + "description": "Attenstation request", + "type": "string" + }, + "attenstationResponse": { + "description": "Attenstation response", + "type": "string" + }, + "uncompressedECPoint": { + "description": "uncompressed EC point", + "type": "string" + }, + "publicKeyId": { + "description": "public key id", + "type": "string" + }, + "type": { + "description": "type", + "type": "string" + }, + "counter": { + "description": "counter", + "type": "integer" + }, + "attestationType": { + "description": "attestation type", + "type": "string" + }, + "signatureAlgorithm": { + "description": "signature algorithm", + "type": "integer" + }, + "applicationId": { + "description": "application id", + "type": "string" + }, + "status": { + "description": "status", + "type": "string", + "enum": [ + "pending", + "registered", + "compromised" + ] + } + } + }, + "Fido2Configuration": { + "type": "object", + "description": "Fido2 configuration properties.", + "properties": { + "authenticatorCertsFolder": { + "description": "Authenticators certificates fodler.", + "type": "string" + }, + "mdsCertsFolder": { + "description": "MDS TOC root certificates folder.", + "type": "string" + }, + "mdsTocsFolder": { + "description": "MDS TOC files folder.", + "type": "string" + }, + "serverMetadataFolder": { + "description": "Authenticators metadata in json format.", + "type": "string" + }, + "requestedParties": { + "description": "Authenticators metadata in json format.", + "type": "array", + "items": { + "$ref": "#/components/schemas/RequestedParties" + } + }, + "userAutoEnrollment": { + "description": "Allow to enroll users on enrollment/authentication requests.", + "type": "boolean" + }, + "unfinishedRequestExpiration": { + "description": "Expiration time in seconds for pending enrollment/authentication requests", + "type": "integer" + }, + "authenticationHistoryExpiration": { + "description": "Expiration time in seconds for approved authentication requests.", + "type": "integer" + }, + "requestedCredentialTypes": { + "description": "List of Requested Credential Types.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "RequestedParties": { + "type": "object", + "description": "Credential Type.", + "properties": { + "name": { + "description": "Name of the Requested Party.", + "type": "string", + "format": "url" + }, + "domains": { + "description": "Requested Party domains.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "SimpleCustomProperty": { + "type": "object", + "description": "Simple Property.", + "properties": { + "value1": { + "type": "string" + }, + "value2": { + "type": "string" + }, + "description": { + "type": "string" + } + } + }, + "SimpleExtendedCustomProperty": { + "type": "object", + "description": "Simple Extended Property.", + "properties": { + "value1": { + "type": "string" + }, + "value2": { + "type": "string" + }, + "description": { + "type": "string" + }, + "hide": { + "type": "boolean" + } + } + }, + "ScriptError": { + "type": "object", + "description": "Possible errors assosiated with the script.", + "properties": { + "raisedAt": { + "type": "string", + "format": "date-time" + }, + "stackTrace": { + "type": "string" + } + } + }, + "AuthenticationFilters": { + "type": "object", + "description": "Represents the authentication filter.", + "properties": { + "filter": { + "type": "string", + "description": "Filter to be used.", + "example": "myCustomAttr1={0}" + }, + "bind": { + "type": "boolean", + "description": "Filter bind." + }, + "bind-password-attribute": { + "type": "string", + "description": "Filter bind password attribute." + }, + "base-dn": { + "type": "string", + "description": "Bind filter base distinguished name.", + "example": "ou=clients,o=gluu" + } + } + }, + "CorsConfigurationFilter": { + "type": "object", + "description": "CORS Configuration Filter.", + "properties": { + "filterName": { + "type": "string", + "description": "Filter name." + }, + "corsEnabled": { + "type": "boolean", + "description": "Boolean value indicating if the filter is enabled." + }, + "corsAllowedOrigins": { + "type": "string", + "description": "A list of origins that are allowed to access the resource. A * can be specified to enable access to resource from any origin. Otherwise, a whitelist of comma separated origins can be provided." + }, + "corsAllowedMethods": { + "type": "string", + "description": "A comma separated list of HTTP methods that can be used to access the resource, using cross-origin requests. These are the methods which will also be included as part of Access-Control-Allow-Methods header in pre-flight response." + }, + "corsAllowedHeaders": { + "type": "string", + "description": "The names of the supported author request headers." + }, + "corsExposedHeaders": { + "type": "string", + "description": "A comma separated list of request headers that can be used when making an actual request. These headers will also be returned as part of Access-Control-Allow-Headers header in a pre-flight response." + }, + "corsSupportCredentials": { + "type": "boolean", + "description": "A flag that indicates whether the resource supports user credentials. This flag is exposed as part of Access-Control-Allow-Credentials header in a pre-flight response. It helps browser determine whether or not an actual request can be made using credentials." + }, + "corsLoggingEnabled": { + "type": "boolean", + "description": "Value to enable logging, Setting the value to False will disable logging." + }, + "corsPreflightMaxAge": { + "type": "integer", + "description": "The duration in seconds the browser is allowed to cache the result of the pre-flight request." + }, + "corsRequestDecorate": { + "type": "boolean", + "description": "A flag to control if CORS specific attributes should be added to the HttpServletRequest object." + } + } + }, + "AuthenticationProtectionConfiguration": { + "type": "object", + "description": "Authentication Brute Force Protection Configuration.", + "properties": { + "attemptExpiration": { + "type": "integer", + "description": "How long store in cache information about particular login attempt. It's needed to count login attempts withing specified period of time." + }, + "maximumAllowedAttemptsWithoutDelay": { + "type": "integer", + "description": "How many attempts application allow without delay." + }, + "delayTime": { + "type": "integer", + "description": "Delay time in seconds after reaching maximumAllowedAttemptsWithoutDelay limit." + }, + "bruteForceProtectionEnabled": { + "type": "boolean", + "description": "Enable or disable service, This functionality can be enabled dynamically." + } + } + }, + "CIBAEndUserNotificationConfig": { + "type": "object", + "description": "CIBA End User Notification Config.", + "properties": { + "apiKey": { + "type": "string", + "description": "API Key" + }, + "authDomain": { + "type": "string", + "description": "Auth Domain" + }, + "databaseURL": { + "type": "string", + "description": "Database URL" + }, + "projectId": { + "type": "string", + "description": "Project ID" + }, + "storageBucket": { + "type": "string", + "description": "Storage Bucket" + }, + "messagingSenderId": { + "type": "string", + "description": "Messaging Sender ID" + }, + "appId": { + "type": "string", + "description": "App ID" + }, + "notificationUrl": { + "type": "string", + "description": "Notification URL" + }, + "notificationKey": { + "type": "string", + "description": "Notification Key" + }, + "publicVapidKey": { + "type": "string", + "description": "Public Vapid Key" + } + } + }, + "StatResponseItem": { + "type": "object", + "description": "Server statistics data", + "properties": { + "month": { + "type": "integer" + }, + "monthly_active_users": { + "type": "integer", + "format": "int64", + "description": "Number of active users", + "default": 0 + }, + "token_count_per_granttype": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/TokenMapObject" + } + } + } + }, + "TokenMapObject": { + "type": "object", + "description": "A hashmap with statistical item as a key and the value as statistical value.", + "additionalProperties": { + "type": "integer", + "format": "int64" + } + }, + "HealthStatus": { + "type": "object", + "description": "Server health data", + "properties": { + "status": { + "type": "string", + "description": "Health parameter name" + }, + "error": { + "type": "string", + "description": "error message in case of error" + }, + "checks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HealthStatusItem", + "description": "health check status details.", + "example": "\"checks\": [{\"name\": \"jans-config-api liveness\",\"status\": \"UP\"},{\"name\": \"jans-config-api readiness\",\"status\": \"UP\"}],\"status\": \"UP\"}" + } + } + } + }, + "HealthStatusItem": { + "type": "object", + "description": "Server health data", + "properties": { + "name": { + "type": "string", + "description": "Health parameter name" + }, + "status": { + "type": "string", + "description": "Health parameter status" + } + } + }, + "SearchRequest": { + "type": "object", + "description": "Search Parameters.", + "properties": { + "schemas": { + "type": "array", + "items": { + "type": "string" + }, + "description": "schema details" + }, + "attributes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "attribute details" + }, + "excludedAttributes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "attribute to be excluded details" + }, + "filter": { + "type": "string", + "description": "search filter" + }, + "sortBy": { + "type": "string", + "description": "attribute to be used for sorting" + }, + "sortOrder": { + "type": "string", + "description": "sorting order" + }, + "startIndex": { + "type": "integer", + "format": "int32", + "description": "result start index" + }, + "count": { + "type": "integer", + "format": "int32", + "description": "total count of records" + } + } + }, + "UserListResponse": { + "description": "Results for users search. See section 3.4.2.4 of RFC 7644", + "allOf": [ + { + "$ref": "#/components/schemas/BasicListResponse" + }, + { + "type": "object" + }, + { + "type": "object", + "properties": { + "Resources": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserResource" + } + } + } + } + ] + }, + "BasicListResponse": { + "type": "object", + "properties": { + "schemas": { + "type": "array", + "items": { + "type": "string", + "example": "urn:ietf:params:scim:api:messages:2.0:ListResponse" + } + }, + "totalResults": { + "type": "integer", + "description": "Total number of results returned by the search. The value may be larger than the number of resources returned due to pagination" + }, + "startIndex": { + "type": "integer", + "description": "The 1-based index of the first result in the current set of search results" + }, + "itemsPerPage": { + "type": "integer", + "description": "The number of resources returned in a results page" + } + } + }, + "UserResource": { + "description": "Represents a user resource. See section 4.1 of RFC 7643", + "allOf": [ + { + "$ref": "#/components/schemas/BaseResource" + }, + { + "type": "object" + }, + { + "type": "object", + "properties": { + "externalId": { + "type": "string", + "description": "Identifier of the resource useful from the perspective of the provisioning client. See section 3.1 of RFC 7643" + }, + "userName": { + "type": "string", + "description": "Identifier for the user, typically used by the user to directly authenticate (id and externalId are opaque identifiers generally not known by users)" + }, + "name": { + "$ref": "#/components/schemas/Name" + }, + "displayName": { + "type": "string", + "description": "Name of the user suitable for display to end-users" + }, + "nickName": { + "type": "string", + "description": "Casual way to address the user in real life" + }, + "profileUrl": { + "type": "string", + "description": "URI pointing to a location representing the User's online profile" + }, + "title": { + "type": "string", + "example": "Vice President" + }, + "userType": { + "type": "string", + "description": "Used to identify the relationship between the organization and the user", + "example": "Contractor" + }, + "preferredLanguage": { + "type": "string", + "description": "Preferred language as used in the Accept-Language HTTP header", + "example": "en" + }, + "locale": { + "type": "string", + "description": "Used for purposes of localizing items such as currency and dates", + "example": "en-US" + }, + "timezone": { + "type": "string", + "example": "America/Los_Angeles" + }, + "active": { + "type": "boolean" + }, + "password": { + "type": "string" + }, + "emails": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Email" + } + }, + "phoneNumbers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PhoneNumber" + } + }, + "ims": { + "type": "array", + "items": { + "$ref": "#/components/schemas/InstantMessagingAddress" + } + }, + "photos": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Photo" + } + }, + "addresses": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Address" + } + }, + "groups": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Group" + } + }, + "entitlements": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Entitlement" + } + }, + "roles": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Role" + } + }, + "x509Certificates": { + "type": "array", + "items": { + "$ref": "#/components/schemas/X509Certificate" + } + }, + "urn:ietf:params:scim:schemas:extension:gluu:2.0:User": { + "type": "object", + "properties": {}, + "description": "Extended attributes" + } + } + } + ] + }, + "Name": { + "type": "object", + "properties": { + "familyName": { + "type": "string" + }, + "givenName": { + "type": "string" + }, + "middleName": { + "type": "string" + }, + "honorificPrefix": { + "type": "string", + "description": "A \"title\" like \"Ms.\", \"Mrs.\"" + }, + "honorificSuffix": { + "type": "string", + "description": "Name suffix, like \"Junior\", \"The great\", \"III\"" + }, + "formatted": { + "type": "string", + "description": "Full name, including all middle names, titles, and suffixes as appropriate" + } + }, + "description": "See section 4.1.1 of RFC 7643" + }, + "Email": { + "type": "object", + "properties": { + "value": { + "description": "E-mail addresses for the user.", + "type": "string", + "example": "gossow@nsfw.com" + }, + "display": { + "description": "A human readable name, primarily used for display purposes.", + "type": "string" + }, + "type": { + "description": "A label indicating the attribute's function; e.g., 'work' or 'home'.", + "type": "string", + "example": "work" + }, + "primary": { + "type": "boolean", + "description": "Denotes if this is the preferred e-mail among others, if any" + } + }, + "description": "See section 4.1.2 of RFC 7643" + }, + "PhoneNumber": { + "type": "object", + "properties": { + "value": { + "description": "Phone number of the User", + "type": "string", + "example": "+1-555-555-8377" + }, + "display": { + "description": "A human readable name, primarily used for display purposes.", + "type": "string" + }, + "type": { + "description": "A label indicating the attribute's function; e.g., 'work' or 'home' or 'mobile' etc.", + "type": "string", + "example": "fax" + }, + "primary": { + "description": "A Boolean value indicating the 'primary' or preferred attribute value for this attribute.", + "type": "boolean" + } + } + }, + "InstantMessagingAddress": { + "type": "object", + "properties": { + "value": { + "description": "Instant messaging address for the User.", + "type": "string" + }, + "display": { + "description": "A human readable name, primarily used for display purposes.", + "type": "string" + }, + "type": { + "description": "A label indicating the attribute's function; e.g., 'aim', 'gtalk', 'mobile' etc.", + "type": "string", + "example": "gtalk" + }, + "primary": { + "type": "boolean", + "description": "Denotes if this is the preferred messaging addressed among others, if any" + } + }, + "description": "See section 4.1.2 of RFC 7643" + }, + "Photo": { + "type": "object", + "properties": { + "value": { + "description": "URI of a photo of the User.", + "type": "string", + "example": "https://pics.nsfw.com/gossow.png" + }, + "display": { + "description": "A human readable name, primarily used for display purposes.", + "type": "string" + }, + "type": { + "description": "A label indicating the attribute's function; e.g., 'photo' or 'thumbnail'.", + "type": "string", + "example": "thumbnail" + }, + "primary": { + "type": "boolean", + "description": "Denotes if this is the preferred photo among others, if any" + } + }, + "description": "Points to a resource location representing the user's image. See section 4.1.2 of RFC 7643" + }, + "Address": { + "type": "object", + "properties": { + "formatted": { + "type": "string", + "description": "The full mailing address, formatted for display or use with a mailing label." + }, + "streetAddress": { + "description": "The full street address component, which may include house number, street name,PO BOX,etc.", + "type": "string", + "example": "56 Acacia Avenue" + }, + "locality": { + "type": "string", + "description": "City or locality of the address" + }, + "region": { + "type": "string", + "description": "State or region of the address" + }, + "postalCode": { + "type": "string", + "description": "Zip code" + }, + "country": { + "type": "string", + "description": "Country expressed in ISO 3166-1 \"alpha-2\" code format", + "example": "UK" + }, + "type": { + "description": "A label indicating the attribute's function; e.g., 'work' or 'home'.", + "type": "string", + "example": "home" + }, + "primary": { + "type": "boolean", + "description": "Denotes if this is the preferred address among others, if any" + } + }, + "description": "Physical mailing address for this user. See section 4.1.2 of RFC 7643" + }, + "Role": { + "type": "object", + "properties": { + "value": { + "description": "The value of a role", + "type": "string", + "example": "Project manager" + }, + "display": { + "description": "A human readable name, primarily used for display purposes.", + "type": "string" + }, + "type": { + "description": "A label indicating the attribute's function.", + "type": "string" + }, + "primary": { + "type": "boolean", + "description": "Denotes if this is the preferred role among others, if any" + } + }, + "description": "See section 4.1.2 of RFC 7643" + }, + "BaseResource": { + "type": "object", + "properties": { + "schemas": { + "type": "array", + "description": "URIs that are used to indicate the namespaces of the SCIM schemas that define the attributes present in the current structure", + "items": { + "type": "string" + } + }, + "id": { + "type": "string", + "description": "A unique identifier for a SCIM resource. See section 3.1 of RFC 7643" + }, + "meta": { + "$ref": "#/components/schemas/Meta" + } + } + }, + "Group": { + "type": "object", + "properties": { + "value": { + "type": "string", + "description": "Group identifier", + "example": "180ee84f0671b1" + }, + "$ref": { + "type": "string", + "description": "URI associated to the group", + "example": "https://nsfw.com/scim/restv1/v2/Groups/180ee84f0671b1" + }, + "display": { + "description": "A human readable name, primarily used for display purposes.", + "type": "string", + "example": "Cult managers" + }, + "type": { + "type": "string", + "description": "Describes how the group membership was derived", + "example": "direct" + } + }, + "description": "See section 4.1.2 of RFC 7643" + }, + "Entitlement": { + "type": "object", + "properties": { + "value": { + "description": "The value of an entitlement.", + "type": "string", + "example": "Stakeholder" + }, + "display": { + "description": "A human readable name, primarily used for display purposes.", + "type": "string" + }, + "type": { + "description": "A label indicating the attribute's function.", + "type": "string" + }, + "primary": { + "type": "boolean", + "description": "Denotes if this is the preferred entitlement among others, if any" + } + }, + "description": "Entitlements represent things a user has, like rights. See section 4.1.2 of RFC 7643" + }, + "X509Certificate": { + "type": "object", + "properties": { + "value": { + "description": "The value of a X509 certificate.", + "type": "string" + }, + "display": { + "description": "A human readable name, primarily used for display purposes.", + "type": "string" + }, + "type": { + "description": "A label indicating the attribute's function.", + "type": "string" + }, + "primary": { + "type": "boolean", + "description": "Denotes if this is the preferred certificate among others, if any" + } + }, + "description": "A certificate associated with the user. See section 4.1.2 of RFC 7643" + }, + "Meta": { + "type": "object", + "properties": { + "resourceType": { + "type": "string" + }, + "created": { + "type": "string" + }, + "lastModified": { + "type": "string" + }, + "location": { + "type": "string" + } + }, + "description": "See section 3.1 of RFC 7643" + }, + "ScimPatchOperation": { + "required": [ + "op" + ], + "type": "object", + "properties": { + "op": { + "type": "string", + "description": "The kind of operation to perform", + "enum": [ + "add", + "remove", + "replace" + ] + }, + "path": { + "type": "string", + "description": "Required when op is remove, optional otherwise" + }, + "value": { + "$ref": "#/components/schemas/AnyValue", + "description": "Only required when op is add or replace" + } + }, + "description": "See section 3.5.2 of RFC 7644" + }, + "ScimPatchRequest": { + "description": "Stores one or more patch operations", + "required": [ + "operations" + ], + "type": "object", + "properties": { + "schemas": { + "type": "array", + "items": { + "type": "string", + "example": "urn:ietf:params:scim:api:messages:2.0:PatchOp" + } + }, + "operations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ScimPatchOperation" + } + } + } + }, + "AnyValue": { + "description": "Can be any value - string, number, boolean, array or object" + }, + "AuthHealthStatus": { + "type": "object", + "description": "Auth Server health data", + "additionalProperties": { + "type": "string" + } + }, + "AdminRole": { + "type": "object", + "description": "Admin role", + "required": [ + "role" + ], + "properties": { + "role": { + "type": "string", + "description": "role" + }, + "description": { + "type": "string", + "description": "role description" + }, + "deletable": { + "type": "boolean", + "description": "can we delete the role?" + } + } + }, + "AdminPermission": { + "type": "object", + "description": "Admin permission", + "required": [ + "permission" + ], + "properties": { + "permission": { + "type": "string", + "description": "permission" + }, + "description": { + "type": "string", + "description": "permission description" + } + } + }, + "RolePermissionMapping": { + "type": "object", + "description": "Admin role-permission mapping", + "required": [ + "role" + ], + "properties": { + "role": { + "type": "string", + "description": "role" + }, + "permissions": { + "type": "array", + "items": { + "type": "string" + }, + "description": "permissions" + } + } + }, + "LicenseDetailsRequest": { + "type": "object", + "description": "Admin license details request", + "required": [ + "validityPeriod" + ], + "properties": { + "validityPeriod": { + "type": "string", + "description": "The license will expire on following date." + }, + "maxActivations": { + "type": "string", + "description": "The maximum allowed activations of this license on different machines." + }, + "licenseActive": { + "type": "string", + "description": "Is license active?" + } + } + }, + "LicenseApiRequest": { + "type": "object", + "description": "Admin license api request", + "required": [ + "licenseKey" + ], + "properties": { + "licenseKey": { + "type": "string", + "description": "The license-key." + } + } + }, + "LicenseSpringCredentials": { + "type": "object", + "required": [ + "apiKey", + "productCode", + "sharedKey", + "managementKey" + ], + "properties": { + "apiKey": { + "type": "string", + "description": "The api-key." + }, + "productCode": { + "type": "string", + "description": "The product-code." + }, + "sharedKey": { + "type": "string", + "description": "The shared-key." + }, + "managementKey": { + "type": "string", + "description": "The management-key." + } + } + }, + "LicenseDetailsResponse": { + "type": "object", + "description": "Admin license details response", + "properties": { + "licenseEnabled": { + "type": "boolean", + "description": "Is license module enabled in admin-ui application?", + "default": false + }, + "productName": { + "type": "string", + "description": "The license is registered under following product." + }, + "productCode": { + "type": "string", + "description": "The short code is used in our API calls in order to identify the product." + }, + "licenseType": { + "type": "string", + "description": "The type of license (eg Perpetual, Time-based, Subscription, and Consumption-based licenses)." + }, + "maxActivations": { + "type": "integer", + "description": "The license key." + }, + "licenseKey": { + "type": "string", + "description": "The license key." + }, + "licenseActive": { + "type": "boolean", + "description": "Is license active?", + "default": false + }, + "validityPeriod": { + "type": "string", + "description": "The license validity period" + }, + "companyName": { + "type": "string", + "description": "The company name of the registered license." + }, + "customerEmail": { + "type": "string", + "description": "The customer email address of the registered license." + }, + "customerFirstName": { + "type": "string", + "description": "The customer first name." + }, + "customerLastName": { + "type": "string", + "description": "The customer last name." + } + } + }, + "LicenseApiResponse": { + "type": "object", + "properties": { + "apiResult": { + "type": "boolean", + "description": "liceseSpring api request success status" + }, + "responseMessage": { + "type": "string", + "description": "Response Message" + }, + "responseCode": { + "type": "integer", + "description": "Response code" + } + } + }, + "ScimAppConfiguration": { + "type": "object", + "properties": { + "baseDN": { + "type": "string", + "description": "Application config Base DN" + }, + "applicationUrl": { + "type": "string", + "description": "Application base URL" + }, + "baseEndpoint": { + "type": "string", + "description": "SCIM base endpoint URL" + }, + "personCustomObjectClass": { + "type": "string", + "description": "Person Object Class" + }, + "oxAuthIssuer": { + "type": "string", + "description": "Jans Auth - Issuer identifier." + }, + "protectionMode": { + "type": "string", + "enum": [ + "OAUTH", + "BYPASS" + ], + "description": "SCIM Protection Mode" + }, + "maxCount": { + "type": "integer", + "example": "Maximum number of results per page" + }, + "userExtensionSchemaURI": { + "type": "string", + "description": "User Extension Schema URI" + }, + "loggingLevel": { + "type": "string", + "description": "Logging level for scim logger.", + "enum": [ + "TRACE", + "DEBUG", + "INFO", + "WARN", + "ERROR", + "FATAL", + "OFF" + ] + }, + "loggingLayout": { + "type": "string", + "description": "Logging layout used for Server loggers." + }, + "externalLoggerConfiguration": { + "type": "string", + "description": "Path to external log4j2 logging configuration." + }, + "metricReporterInterval": { + "type": "integer", + "description": "The interval for metric reporter in seconds." + }, + "metricReporterKeepDataDays": { + "type": "integer", + "description": "The days to keep metric reported data." + }, + "metricReporterEnabled": { + "type": "boolean", + "description": "Metric reported data enabled flag." + }, + "disableJdkLogger": { + "type": "boolean", + "description": "Boolean value specifying whether to enable JDK Loggers." + }, + "useLocalCache": { + "type": "boolean", + "description": "Boolean value specifying whether to enable local in-memory cache." + } + } + }, + "Organization": { + "type": "object", + "properties": { + "displayName": { + "type": "string", + "description": "Organization name" + }, + "description": { + "type": "string", + "description": "Organization description" + }, + "member": { + "type": "string", + "description": "String describing memberOf" + }, + "countryName": { + "type": "string", + "description": "Organization country name" + }, + "organization": { + "type": "string" + }, + "status": { + "type": "string" + }, + "managerGroup": { + "type": "string", + "description": "qualified id of the group", + "example": "inum=60B7,ou=groups,o=jans" + }, + "themeColor": { + "type": "string", + "description": "color of the theme", + "example": 166309 + }, + "shortName": { + "type": "string" + }, + "customMessages": { + "type": "array", + "items": { + "type": "string" + } + }, + "title": { + "type": "string" + }, + "jsLogoPath": { + "type": "string", + "description": "Path to organization logo image" + }, + "jsFaviconPath": { + "type": "string", + "description": "Path to organization favicon image" + } + } + }, + "FacterData": { + "type": "object", + "properties": { + "memoryfree": { + "type": "string", + "description": "Server free memory" + }, + "swapfree": { + "type": "string", + "description": "Server swap free" + }, + "hostname": { + "type": "string", + "description": "Server hostname" + }, + "ipaddress": { + "type": "string", + "description": "Server ipaddress" + }, + "uptime": { + "type": "string", + "description": "Server uptime" + }, + "free_disk_space": { + "type": "string", + "description": "Server free disk space" + }, + "load_average": { + "type": "string", + "description": "Server average load time" + } + } + }, + "StatsData": { + "type": "object", + "properties": { + "dbType": { + "type": "string", + "description": "Jans Server DB type" + }, + "lastUpdate": { + "type": "string", + "description": "Stats update time" + }, + "facterData": { + "type": "object", + "$ref": "#/components/schemas/FacterData", + "description": "Underlying Server stats" + } + } + }, + "CustomUser": { + "title": "User object", + "description": "User.", + "type": "object", + "required": [ + "userId", + "mail", + "displayName", + "givenName", + "jansStatus" + ], + "properties": { + "dn": { + "type": "string", + "description": "Domain name." + }, + "baseDN": { + "type": "string", + "description": "Base DN for the User entity" + }, + "jansStatus": { + "type": "string", + "description": "User status", + "enum": [ + "ACTIVE", + "INACTIVE", + "EXPIRED", + "REGISTER" + ] + }, + "userId": { + "description": "A domain issued and managed identifier for the user.", + "type": "string" + }, + "createdAt": { + "description": "User creation date.", + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "description": "Time the information of the person was last updated. Seconds from 1970-01-01T0:0:0Z", + "type": "string", + "format": "date-time" + }, + "oxAuthPersistentJwt": { + "description": "Persistent JWT.", + "type": "array", + "items": { + "type": "string" + } + }, + "customAttributes": { + "description": "dn of associated clients with the user.", + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomAttribute" + } + }, + "mail": { + "type": "string", + "description": "User mail" + }, + "displayName": { + "type": "string", + "description": "Name of the user suitable for display to end-users" + }, + "givenName": { + "type": "string", + "description": "User given Name" + }, + "userPassword": { + "type": "string", + "description": "User password" + }, + "inum": { + "description": "XRI i-number. Identifier to uniquely identify the user.", + "type": "string" + } + } + }, + "ExtendedCustomUser": { + "allOf": [ + { + "$ref": "#/components/schemas/CustomUser" + }, + { + "type": "object", + "required": [ + "userPassword" + ], + "properties": { + "userPassword": { + "type": "string", + "description": "User password" + } + } + } + ] + }, + "UserPatchRequest": { + "title": "User Patch Request object", + "description": "UserPatchRequest.", + "type": "object", + "properties": { + "jsonPatchString": { + "type": "object", + "description": "Possible errors assosiated with the script.", + "$ref": "#/components/schemas/PatchRequest" + }, + "customAttributes": { + "description": "dn of associated clients with the user.", + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomAttribute" + } + } + } + }, + "EngineConfig": { + "title": "Engine config object", + "description": "Engine config object that offers an alternative way to build authentication flows in Janssen server.", + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "boolean value indicating if agama configuration enabled.", + "default": false + }, + "templatesPath": { + "type": "string", + "description": "path to the templates", + "default": "/ftl" + }, + "scriptsPath": { + "type": "string", + "description": "path to the scripts", + "default": "/scripts" + }, + "serializerType": { + "type": "string", + "description": "type of supported serializer", + "default": "KRYO", + "enum": [ + "KRYO", + "FST" + ] + }, + "maxItemsLoggedInCollections": { + "type": "integer", + "description": "maximum logged in collection item", + "default": 3, + "minimum": 1 + }, + "pageMismatchErrorPage": { + "type": "string", + "description": "mismatch error page.", + "default": "mismatch.ftl" + }, + "interruptionErrorPage": { + "type": "string", + "description": "interruption error page.", + "default": "timeout.ftl" + }, + "crashErrorPage": { + "type": "string", + "description": "crash error page.", + "default": "crash.ftl" + }, + "finishedFlowPage": { + "type": "string", + "description": "finished flow page.", + "default": "finished.ftl" + }, + "bridgeScriptPage": { + "type": "string", + "description": "bridge script page.", + "default": "agama.xhtml" + }, + "defaultResponseHeaders": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } +} diff --git a/jans-cli-tui/cli_style.py b/jans-cli-tui/cli_style.py new file mode 100644 index 00000000000..d1ec76f0376 --- /dev/null +++ b/jans-cli-tui/cli_style.py @@ -0,0 +1,21 @@ +from prompt_toolkit.styles import Style + +style = Style.from_dict( + { + "window.border": "#888888", + "shadow": "bg:#222222", + "menu-bar": "bg:#aaaaaa #888888", + "menu-bar.selected-item": "bg:#ffffff #000000", + "menu": "bg:#888888 #ffffff", + "menu.border": "#aaaaaa", + "window.border shadow": "#444444", + "focused button": "bg:#880000 #ffffff noinherit", + # Styling for Dialog widgets. + "button-bar": "bg:#4D4D4D", + "text-area focused": "bg:#ff0000", + "status": "reverse", + "select-box cursor-line": "nounderline bg:ansired fg:ansiwhite", + "textarea":"nounderline bg:#ffffff fg:#d1c0c0 #ff0000", + "checkbox":"nounderline bg:#ffffff fg:#d1c0c0 #ff0000", + } +) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py new file mode 100755 index 00000000000..e9fa9d4fd4e --- /dev/null +++ b/jans-cli-tui/jans-cli-tui.py @@ -0,0 +1,356 @@ +#!/usr/bin/env python3 +""" +""" +import json +import os +from shutil import get_terminal_size +import time +from asyncio import Future, ensure_future + +import prompt_toolkit +from prompt_toolkit.application import Application +from prompt_toolkit.application.current import get_app +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.key_binding.bindings.focus import focus_next, focus_previous +from prompt_toolkit.layout.containers import Float, HSplit, VSplit +from prompt_toolkit.layout.containers import ( + ConditionalContainer, + Float, + HSplit, + VSplit, + VerticalAlign, + DynamicContainer, + FloatContainer, +) +from prompt_toolkit.layout.containers import VerticalAlign +from prompt_toolkit.layout.dimension import D +from prompt_toolkit.layout.layout import Layout +from prompt_toolkit.lexers import PygmentsLexer ,DynamicLexer +from prompt_toolkit.widgets import ( + Box, + Button, + Frame, + Label, + RadioList, + TextArea, + CheckboxList, + Shadow, +) +from prompt_toolkit.filters import Condition + +# -------------------------------------------------------------------------- # +from cli import config_cli +from wui_components.edit_client_dialog import EditClientDialog +from wui_components.edit_scope_dialog import EditScopeDialog +from wui_components.jans_cli_dialog import JansGDialog +from wui_components.jans_nav_bar import JansNavBar +from wui_components.jans_side_nav_bar import JansSideNavBar +from wui_components.jans_vetrical_nav import JansVerticalNav +from wui_components.jans_dialog import JansDialog +from wui_components.jans_dialog_with_nav import JansDialogWithNav + +from cli_style import style + +from models.oauth import JansAuthServer +from pathlib import Path + +# -------------------------------------------------------------------------- # + + +help_text_dict = { + 'displayName': ("Name of the user suitable for display to end-users"), + 'clientSecret': ("The client secret. The client MAY omit the parameter if the client secret is an empty string"), + 'redirectUris': ("Redirection URI values used by the Client. One of these registered Redirection URI values must exactly match the redirect_uri parameter value used in each Authorization Request"), + 'responseTypes': ("A list of the OAuth 2.0 response_type values that the Client is declaring that it will restrict itself to using. If omitted, the default is that the Client will use only the code Response Type. Allowed values are code, token, id_token"), + 'applicationType': ("Kind of the application. The default, if omitted, is web. The defined values are native or web. Web Clients using the OAuth Implicit Grant Type must only register URLs using the HTTPS scheme as redirect_uris, they must not use localhost as the hostname. Native Clients must only register redirect_uris using custom URI schemes or URLs using the http scheme with localhost as the hostname"), + 'helper': ("To guide you through the fields"), +} + +home_dir = Path.home() +config_dir = home_dir.joinpath('.config') +config_dir.mkdir(parents=True, exist_ok=True) +config_ini_fn = config_dir.joinpath('jans-cli.ini') + +def accept_yes(): + get_app().exit(result=True) + + +def accept_no(): + get_app().exit(result=False) + +def do_exit(*c): + get_app().exit(result=False) + + +class JansCliApp(Application, JansAuthServer): + + def __init__(self): + self.app_started = False + self.width, self.height = get_terminal_size() + self.app = get_app() + self.show_dialog = False ## ## ## ## + self.set_keybindings() + self.containers = {} + # -------------------------------------------------------------------------------- # + self.dialogs = {} + self.tabs = {} + self.active_dialog_select = '' + self.Auth_clients_tabs = {} + + # ----------------------------------------------------------------------------- # + self.check_jans_cli_ini() + # ----------------------------------------------------------------------------- # + + self.yes_button = Button(text="Yes", handler=accept_yes) + self.no_button = Button(text="No", handler=accept_no) + self.status_bar = TextArea(style="class:status", height=1, focusable=False) + + self.prapare_dialogs() + + JansAuthServer.initialize(self) + + + self.not_implemented = Frame( + body=HSplit([Label(text="Not imlemented yet"), Button(text="MyButton")], width=D()), + height=D()) + + self.center_container = self.not_implemented + + self.nav_bar = JansNavBar( + self, + entries=[('oauth', 'Auth Server'), ('fido', 'FDIO'), ('scim', 'SCIM'), ('config_api', 'Config-API'), ('client_api', 'Client-API'), ('scripts', 'Scripts')], + selection_changed=self.main_nav_selection_changed, + select=0, + ) + + self.center_frame = FloatContainer(content= + Frame( + body=DynamicContainer(lambda: self.center_container), + height=D() + ), + floats=[], + ) + + + self.root_layout = FloatContainer( + HSplit([ + Frame(self.nav_bar.nav_window), + self.center_frame, + self.status_bar + ], + ), + floats=[] + ) + + super(JansCliApp, self).__init__( + layout=Layout(self.root_layout), + key_bindings=self.bindings, + style=style, + full_screen=True, + mouse_support=True, ## added + ) + + + + self.app_started = True + self.main_nav_selection_changed(self.nav_bar.navbar_entries[0][0]) + + # Since first module is oauth, set center frame to my oauth main container. + self.oauth_set_center_frame() + + def create_cli(self): + test_client = config_cli.client_id if config_cli.test_client else None + self.cli_object = config_cli.JCA_CLI( + host=config_cli.host, + client_id=config_cli.client_id, + client_secret=config_cli.client_secret, + access_token=config_cli.access_token, + test_client=test_client, + wrapped=True + ) + + def check_jans_cli_ini(self): + if not(config_cli.host and (config_cli.client_id and config_cli.client_secret or config_cli.access_token)): + self.jans_creds_dialog() + else : + self.create_cli() + + def dialog_back_but(self): ## BACK + self.active_dialog_select = '' + self.show_dialog = False + self.layout.focus(self.center_frame) + + + def prapare_dialogs(self): + self.data_show_client_dialog = Label(text='Selected Line Data as Json') + self.dialog_width = int(self.width*0.9) # 120 ## to be dynamic + self.dialog_height = int(self.height*0.8) ## to be dynamic + + + def focus_next(self, ev): + focus_next(ev) + self.update_status_bar() + + def focus_previous(self, ev): + focus_previous(ev) + self.update_status_bar() + + def set_keybindings(self): + # Global key bindings. + self.bindings = KeyBindings() + self.bindings.add("tab")(self.focus_next) + self.bindings.add("s-tab")(self.focus_previous) + self.bindings.add("c-c")(do_exit) + + def getTitledText(self, title, name, height=1, jans_help='', width=None): + multiline = height > 1 + ta = TextArea(multiline=multiline, style='class:textarea') + ta.window.jans_name = name + ta.window.jans_help = jans_help + if width: + ta.window.width = width + li = title + for i in range(height-1): + li +='\n' + + return VSplit([Label(text=li + ':', width=len(title)+1), ta], height=height, padding=1) + + def getTitledCheckBox(self, title, name, values): + cb = CheckboxList(values=[(o,o) for o in values],) + cb.window.jans_name = name + li = title + for i in range(len(values)-1): + li +='\n' + return VSplit([Label(text=li, width=len(title)+1), cb] ) + + def getTitledRadioButton(self, title, name, values): + rl = RadioList(values=[(option, option) for option in values]) + rl.window.jans_name = name + li = title + for i in range(len(values)-1): + li +='\n' + + return VSplit([Label(text=li, width=len(title)+1), rl], + height=len(values) + ) + + def getButton(self, text, name, jans_help, handler=None): + b = Button(text=text, width=len(text)+2) + b.window.jans_name = name + b.window.jans_help = jans_help + if handler: + b.handler = handler + return b + + def get_statusbar_text(self): + wname = getattr(self.layout.current_window, 'jans_name', 'NA') + return help_text_dict.get(wname, '') + + def update_status_bar(self, text=None): + if text: + self.status_bar.text = text + else: + if hasattr(self.layout.current_window, 'jans_help') and self.layout.current_window.jans_help: + text = self.layout.current_window.jans_help + else: + wname = getattr(self.layout.current_window, 'jans_name', 'NA') + text = help_text_dict.get(wname, '') + + self.status_bar.text = text + + def main_nav_selection_changed(self, selection): + if hasattr(self, selection+'_set_center_frame'): + center_frame_setter = getattr(self, selection+'_set_center_frame') + center_frame_setter() + else: + self.center_container = self.not_implemented + + async def show_dialog_as_float(self, dialog): + "Coroutine." + float_ = Float(content=dialog) + self.root_layout.floats.insert(0, float_) + + app = get_app() + + focused_before = app.layout.current_window + app.layout.focus(dialog) + result = await dialog.future + app.layout.focus(focused_before) + + if float_ in self.root_layout.floats: + self.root_layout.floats.remove(float_) + + return result + + + def data_display_dialog(self, **params): + + body = HSplit([ + TextArea( + lexer=DynamicLexer(lambda: PygmentsLexer.from_filename(".json", sync_from_start=True)), + scrollbar=True, + line_numbers=True, + multiline=True, + read_only=True, + text=str(json.dumps(params['data'], indent=2)), + ) + ]) + + dialog = JansGDialog(title=params['selected'][0], body=body) + + self.show_jans_dialog(dialog) + + def save_creds(self, dialog): + + for child in dialog.body.children: + prop_name = child.children[1].jans_name + prop_val = child.children[1].content.buffer.text + config_cli.config['DEFAULT'][prop_name] = prop_val + config_cli.write_config() + + config_cli.host = config_cli.config['DEFAULT']['jans_host'] + config_cli.client_id = config_cli.config['DEFAULT']['jca_client_id'] + config_cli.client_secret = config_cli.config['DEFAULT']['jca_client_secret'] + + self.create_cli() + + def show_jans_dialog(self, dialog): + async def coroutine(): + self.layout.focus(dialog) + result = await self.show_dialog_as_float(dialog) + return result + ensure_future(coroutine()) + + def jans_creds_dialog(self): + + body=HSplit([ + self.getTitledText("Hostname", name='jans_host', jans_help="FQN name of Jannsen Config Api Server"), + self.getTitledText("Client ID", name='jca_client_id', jans_help="Jannsen Config Api Client ID"), + self.getTitledText("Client Secret", name='jca_client_secret', jans_help="Jannsen Config Api Client Secret") + ]) + + buttons = [Button("Save", handler=self.save_creds)] + dialog = JansGDialog(title="Janssen Config Api Client Credidentials", body=body, buttons=buttons) + + self.show_jans_dialog(dialog) + + + def edit_client_dialog(self, **params): + dialog = EditClientDialog(self,**params) + self.show_jans_dialog(dialog) + + def edit_scope_dialog(self, **params): + dialog = EditScopeDialog(self,**params) + self.show_jans_dialog(dialog) + + + +application = JansCliApp() + +def run(): + result = application.run() + print("You said: %r" % result) + + +if __name__ == "__main__": + run() diff --git a/jans-cli-tui/models/oauth.py b/jans-cli-tui/models/oauth.py new file mode 100644 index 00000000000..8ca4a5664ba --- /dev/null +++ b/jans-cli-tui/models/oauth.py @@ -0,0 +1,285 @@ +from curses import window +import threading + +from collections import OrderedDict +import json +from asyncio import Future, ensure_future + +from prompt_toolkit.application.current import get_app +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.key_binding.bindings.focus import focus_next, focus_previous +from prompt_toolkit.layout.containers import ( + ConditionalContainer, + Float, + HSplit, + VSplit, + VerticalAlign, + DynamicContainer, + FloatContainer, + Window +) +from prompt_toolkit.layout.containers import VerticalAlign +from prompt_toolkit.layout.dimension import D +from prompt_toolkit.layout.layout import Layout +from prompt_toolkit.lexers import PygmentsLexer ,DynamicLexer +from prompt_toolkit.widgets import ( + Box, + Button, + Frame, + Label, + RadioList, + TextArea, + CheckboxList, + Shadow, +) +from prompt_toolkit.filters import Condition + +from cli import config_cli +from wui_components.jans_nav_bar import JansNavBar +from wui_components.jans_side_nav_bar import JansSideNavBar +from wui_components.jans_vetrical_nav import JansVerticalNav +from wui_components.jans_dialog import JansDialog +from wui_components.jans_dialog_with_nav import JansDialogWithNav + + +class JansAuthServer: + + + def initialize(self): + self.oauth_containers = {} + self.oauth_prepare_navbar() + self.oauth_prepare_containers() + self.oauth_prepare_tabs() + self.oauth_nav_selection_changed(self.oauth_navbar.navbar_entries[0][0]) + + + def oauth_prepare_tabs(self): + self.oauth_tabs = {} + self.oauth_tabs['clients'] = OrderedDict() + + self.oauth_tabs['clients']['Person Authentication'] = HSplit([ + VSplit([ + # Label(text=str('Client_ID')), + #TextArea(text='', name='Client_ID'), + self.getTitledText("Client_ID", name='Client_ID'), + Window(width=1, char=" ",), + self.getTitledCheckBox("Active:", name='active', values=['active']), + ]) , + self.getTitledText("Client Name:", name='displayName'), + self.getTitledText("Client Secret:", name='clientSecret'), + self.getTitledText("Description:", name='Description'), + self.getTitledRadioButton("Id_token Subject Type:", name='Id_token', values=['Pairwise','Public'],), + self.getTitledCheckBox("Grant:", name='Grant', values=['Authorizatuin Code', 'Refresh Token', 'UMA Ticket','Client Credential','Password','Implicit']), + self.getTitledCheckBox("Response Types:", name='responseTypes', values=['code', 'token', 'id_token']), + self.getTitledCheckBox("Supress Authorization:", name='Supress', values=['True']), + self.getTitledRadioButton("Application Type:", name='applicationType', values=['web', 'native']), + self.getTitledText("Redirect Uris:", name='redirectUris', height=3), + self.getTitledText("Redirect Regex:", name='redirectregex'), + self.getTitledText("Scopes:", name='scopes', height=3), + + + HSplit([Box(body=VSplit([self.yes_button, self.no_button], align="CENTER", padding=3), style="class:button-bar", height=1)], + height=D(), + align = VerticalAlign.BOTTOM, + ) + ], + ) + self.oauth_tabs['clients']['Consent Gathering'] = Label(text=str('Consent Gathering')) + self.oauth_tabs['clients']['Post Authentication'] = Label(text=str('Post Authentication')) + self.oauth_tabs['clients']['id_token'] = Label(text=str('id_token')) + self.oauth_tabs['clients']['Password Grant'] = Label(text=str('Password Grant')) + self.oauth_tabs['clients']['CIBA End User Notification'] = Label(text=str('CIBA End User Notification')) + self.oauth_tabs['clients']['OpenId Configuration'] = Label(text=str('OpenId Configuration')) + self.oauth_tabs['clients']['Dynamic Scope'] = Label(text=str('Dynamic Scope')) + self.oauth_tabs['clients']['Spontaneous Scope'] = Label(text=str('Spontaneous Scope')) + self.oauth_tabs['clients']['End Session'] = Label(text=str('End Session')) + self.oauth_tabs['clients']['Client Registation'] = Label(text=str('Client Registation')) + self.oauth_tabs['clients']['Introseption'] = Label(text=str('Introseption')) + self.oauth_tabs['clients']['Update Token'] = Label(text=str('Update Token')) + + self.oauth_dialog_nav = list(self.oauth_tabs['clients'].keys())[0] + + def client_dialog_nav_selection_changed(self, selection): + self.oauth_dialog_nav = selection + + def edit_client(self, selected,event,size): ## enter + self.edit_client_dialog() + # self.active_dialog_select = 'enter' + # self.show_dialog = True + + # event.app.layout.focus(self.my_dialogs()) + + def oauth_prepare_containers(self): + + self.oauth_data_container = { + 'clients' :HSplit([],width=D()), + 'scopes' :HSplit([],width=D()), + + } + self.oauth_main_area = HSplit([],width=D()) + + self.oauth_containers['scopes'] = HSplit([ + VSplit([ + self.getButton(text="Get Scopes", name='oauth:scopes:get', jans_help="Retreive first 10 Scopes", handler=self.oauth_get_scopes), + self.getTitledText('Search: ', name='oauth:scopes:search', jans_help='Press enter to perform search'), + self.getButton(text="Add Scope", name='oauth:scopes:add', jans_help="To add a new scope press this button") + ], + padding=3, + width=D(), + ), + DynamicContainer(lambda: self.oauth_data_container['scopes']) + ]) + + self.oauth_containers['clients'] = HSplit([ + VSplit([ + self.getButton(text="Get Clients", name='oauth:clients:get', jans_help="Retreive first 10 OpenID Connect clients", handler=self.oauth_get_clients), + self.getTitledText('Search: ', name='oauth:clients:search', jans_help='Press enter to perform search'), + self.getButton(text="Add Client", name='oauth:clients:add', jans_help="To add a new client press this button") + ], + padding=3, + width=D(), + ), + DynamicContainer(lambda: self.oauth_data_container['clients']) + ] + ) + + self.oauth_main_container = HSplit([ + Box(self.oauth_navbar.nav_window, style='fg:#f92672 bg:#4D4D4D', height=1), + DynamicContainer(lambda: self.oauth_main_area), + ], + height=D(), + ) + + self.dialogs['oauth:clients:d'] = JansDialog( + entries_list=[[DynamicContainer(lambda:self.data_show_client_dialog) ,'']], + title='Show client Dialog', + button_functions=[[self.dialog_back_but,"Back"]], + height=self.dialog_height, + width=self.dialog_width, + only_view=True + ) + + self.dialogs['oauth:scopes:d'] = JansDialog( + entries_list=[[DynamicContainer(lambda:self.data_show_client_dialog) ,'']], + title='Show scopes Dialog', + button_functions=[[self.dialog_back_but,"Back"]], + height=self.dialog_height, + width=self.dialog_width, + only_view=True + ) + + def oauth_prepare_navbar(self): + self.oauth_navbar = JansNavBar( + self, + entries=[('clients', 'Clients'), ('scopes', 'Scopes'), ('keys', 'Keys'), ('defaults', 'Defaults'), ('properties', 'Properties'), ('logging', 'Logging')], + selection_changed=self.oauth_nav_selection_changed, + select=0, + bgcolor='#66d9ef' + ) + + def oauth_nav_selection_changed(self, selection): + if selection in self.oauth_containers: + self.oauth_main_area = self.oauth_containers[selection] + else: + self.oauth_main_area = self.not_implemented + + def oauth_set_center_frame(self): + self.center_container = self.oauth_main_container + + def oauth_update_clients(self): + try : + result = self.cli_object.process_command_by_id('get-oauth-openid-clients', '', 'limit:10', {}) + + data =[] + + for d in result: + data.append( + [ + d['inum'], + d['clientName']['values'][''], + ','.join(d['grantTypes']), + d['subjectType'] + ] + ) + + clients = JansVerticalNav( + myparent=self, + headers=['Client ID', 'Client Name', 'Grant Types', 'Subject Type'], + preferred_size= [0,0,30,0], + data=data, + on_enter=self.edit_client, + on_display=self.data_display_dialog, + # selection_changed=self.data_selection_changed, + selectes=0, + headerColor='green', + entriesColor='white', + all_data=result + ) + + self.layout.focus(clients) # clients.focuse..!? TODO >> DONE + self.oauth_data_container['clients'] = HSplit([ + clients + ]) + + get_app().invalidate() + + except Exception as e: + self.oauth_data_container['clients'] = HSplit([Label("Faild to Fitch client Data.. Reason: " + str(e))], width=D()) + get_app().invalidate() + + def oauth_get_clients(self): + self.oauth_data_container['clients'] = HSplit([Label("Please wait while getting clients")], width=D()) + t = threading.Thread(target=self.oauth_update_clients, daemon=True) + t.start() + + def update_oauth_scopes(self): + try : + result = self.cli_object.process_command_by_id('get-oauth-scopes', '', 'limit:10', {}) + + data =[] + + + for d in result: + data.append( + [ + d['id'], + d['description'], + d['scopeType'] + ] + ) + + clients = JansVerticalNav( + myparent=self, + headers=['id', 'Description', 'Type'], + preferred_size= [0,0,30,0], + data=data, + on_enter=self.edit_scope, + on_display=self.data_display_dialog, + # selection_changed=self.data_selection_changed, + selectes=0, + headerColor='green', + entriesColor='white', + all_data=result + ) + + self.layout.focus(clients) # clients.focuse..!? TODO >> DONE + self.oauth_data_container['scopes'] = HSplit([ + clients + ]) + + get_app().invalidate() + + except Exception as e: + self.oauth_data_container['scopes'] = HSplit([Label("Faild to Fitch client Data.. Reason: " + str(e))], width=D()) + get_app().invalidate() + + def oauth_get_scopes(self): + self.oauth_data_container['scopes'] = HSplit([Label("Please wait while getting Scopes")], width=D()) + t = threading.Thread(target=self.update_oauth_scopes, daemon=True) + t.start() + + def display_scope(self): + pass + + def edit_scope(self, selected,event,size): ## enter + self.edit_scope_dialog() diff --git a/jans-cli-tui/static.py b/jans-cli-tui/static.py new file mode 100644 index 00000000000..e331702ef8d --- /dev/null +++ b/jans-cli-tui/static.py @@ -0,0 +1,6 @@ +from enum import Enum + +class DialogResult(Enum): + CANCEL = 0 + ACCEPT = 1 + OK = 2 diff --git a/jans-cli-tui/wui_components/edit_client_dialog.py b/jans-cli-tui/wui_components/edit_client_dialog.py new file mode 100644 index 00000000000..ee47573806b --- /dev/null +++ b/jans-cli-tui/wui_components/edit_client_dialog.py @@ -0,0 +1,94 @@ +import json +from asyncio import Future +from typing import OrderedDict + +from prompt_toolkit.widgets import Button, TextArea +from prompt_toolkit.application.current import get_app +from prompt_toolkit.layout.dimension import D +from static import DialogResult +from wui_components.jans_dialog import JansDialog +from prompt_toolkit.layout.containers import ( + VSplit, + DynamicContainer, +) +from prompt_toolkit.widgets import ( + Button, + Label, + TextArea, + +) + +from cli import config_cli +from prompt_toolkit.layout.containers import ( + ConditionalContainer, + Float, + HSplit, + VSplit, + VerticalAlign, + DynamicContainer, + FloatContainer, + Window +) +from prompt_toolkit.widgets import ( + Box, + Button, + Frame, + Label, + RadioList, + TextArea, + CheckboxList, + Shadow, +) +from wui_components.jans_dialog_with_nav import JansDialogWithNav +from wui_components.jans_side_nav_bar import JansSideNavBar + + +class EditClientDialog: + def __init__(self,myparent, **params): + self.myparent = myparent + self.future = Future() + + def accept_text(buf): + get_app().layout.focus(ok_button) + buf.complete_state = None + return True + + def accept(): + self.future.set_result(DialogResult.ACCEPT) + + def cancel(): + self.future.set_result(DialogResult.CANCEL) + + self.side_NavBar = JansSideNavBar(myparent=self.myparent, + entries=list(self.myparent.oauth_tabs['clients'].keys()), + selection_changed=(self.myparent.client_dialog_nav_selection_changed) , + select=0, + entries_color='#2600ff') + + self.dialog = JansDialogWithNav( + title="Edit user Data (Clients)", + navbar=DynamicContainer(lambda:self.side_NavBar), + content=DynamicContainer(lambda: self.myparent.oauth_tabs['clients'][self.myparent.oauth_dialog_nav]), + button_functions=[ + (accept, "Save"), + (cancel, "Cancel") + ], + height=self.myparent.dialog_height, + width=self.myparent.dialog_width, + ) + + + ok_button = Button(text="OK", handler=accept) + cancel_button = Button(text="Cancel", handler=cancel) + buttons = [cancel_button] + #if params.get('ok_button'): + buttons.insert(0, ok_button) + + + + def __pt_container__(self): + return self.dialog + + + + diff --git a/jans-cli-tui/wui_components/edit_scope_dialog.py b/jans-cli-tui/wui_components/edit_scope_dialog.py new file mode 100644 index 00000000000..b051bc58a63 --- /dev/null +++ b/jans-cli-tui/wui_components/edit_scope_dialog.py @@ -0,0 +1,94 @@ +import json +from asyncio import Future +from typing import OrderedDict + +from prompt_toolkit.widgets import Button, TextArea +from prompt_toolkit.application.current import get_app +from prompt_toolkit.layout.dimension import D +from static import DialogResult +from wui_components.jans_dialog import JansDialog +from prompt_toolkit.layout.containers import ( + VSplit, + DynamicContainer, +) +from prompt_toolkit.widgets import ( + Button, + Label, + TextArea, + +) + +from cli import config_cli +from prompt_toolkit.layout.containers import ( + ConditionalContainer, + Float, + HSplit, + VSplit, + VerticalAlign, + DynamicContainer, + FloatContainer, + Window +) +from prompt_toolkit.widgets import ( + Box, + Button, + Frame, + Label, + RadioList, + TextArea, + CheckboxList, + Shadow, +) +from wui_components.jans_dialog_with_nav import JansDialogWithNav +from wui_components.jans_side_nav_bar import JansSideNavBar + + +class EditScopeDialog: + def __init__(self,myparent, **params): + self.myparent = myparent + self.future = Future() + + def accept_text(buf): + get_app().layout.focus(ok_button) + buf.complete_state = None + return True + + def accept(): + self.future.set_result(DialogResult.ACCEPT) + + def cancel(): + self.future.set_result(DialogResult.CANCEL) + + + self.side_NavBar = JansSideNavBar(myparent=self.myparent, + entries=list(self.myparent.oauth_tabs['clients'].keys()), + selection_changed=(self.myparent.client_dialog_nav_selection_changed) , + select=0, + entries_color='#2600ff') + + self.dialog = JansDialogWithNav( + title="Edit Scope Data (Scopes)", + navbar=DynamicContainer(lambda:self.side_NavBar), + content=DynamicContainer(lambda: self.myparent.oauth_tabs['clients'][self.myparent.oauth_dialog_nav]), ## can be diffrent + button_functions=[ + (accept, "Save"), ## button name is changed to make sure it is another one + (cancel, "Cancel") + ], + height=self.myparent.dialog_height, + width=self.myparent.dialog_width, + ) + + + ok_button = Button(text="OK", handler=accept) + cancel_button = Button(text="Cancel", handler=cancel) + buttons = [cancel_button] + #if params.get('ok_button'): + buttons.insert(0, ok_button) + + + def __pt_container__(self): + return self.dialog + + + + diff --git a/jans-cli-tui/wui_components/jans_cli_dialog.py b/jans-cli-tui/wui_components/jans_cli_dialog.py new file mode 100644 index 00000000000..29dbff9d62b --- /dev/null +++ b/jans-cli-tui/wui_components/jans_cli_dialog.py @@ -0,0 +1,35 @@ +import json +from asyncio import Future + +from prompt_toolkit.widgets import Button, Dialog, TextArea +from prompt_toolkit.layout.containers import HSplit +from prompt_toolkit.application.current import get_app +from prompt_toolkit.layout.dimension import D +from static import DialogResult +from functools import partial +class JansGDialog: + def __init__(self, title, body, buttons=[]): + self.future = Future() + self.body = body + + if not buttons: + buttons = [Button(text="OK")] + + def do_handler(button_text, handler): + if handler: + handler(self) + self.future.set_result(button_text) + + for button in buttons: + button.handler = partial(do_handler, button.text, button.handler) + + self.dialog = Dialog( + title=title, + body=body, + buttons=buttons, + width=D(preferred=80), + modal=True, + ) + + def __pt_container__(self): + return self.dialog diff --git a/jans-cli-tui/wui_components/jans_dialog.py b/jans-cli-tui/wui_components/jans_dialog.py new file mode 100644 index 00000000000..91c7d5736ec --- /dev/null +++ b/jans-cli-tui/wui_components/jans_dialog.py @@ -0,0 +1,46 @@ +from prompt_toolkit.widgets import ( + Button, + Dialog, +) +from prompt_toolkit.layout.containers import HSplit + + +class JansDialog(): + def __init__(self,only_view=False,height=None,width=None,title=None, button_functions=[], entries_list=[], entries_color='#00ff44'): + self.entries_list = entries_list + self.button_functions = button_functions + self.entries_color = entries_color + self.title = title + self.height = height + self.width =width + self.only_view=only_view + self.create_window() + + def create_window(self): + ### get max title len + max_title_str = self.entries_list[0][1] # list is not empty + for x in self.entries_list: + if len(x[1]) > len(max_title_str): + max_title_str = x[1] + + max_data_str = 41 ## TODO TO BE Dynamic + self.dialog = Dialog( + title=str(self.title), + body=HSplit( + [ + self.entries_list[i][0]for i in range(len(self.entries_list)) + ],height=self.height, + width= self.width + ), + buttons=[ + Button( + text=str(self.button_functions[k][1]), + handler=self.button_functions[k][0], + ) for k in range(len(self.button_functions)) + ], + with_background=False, + ) +#--------------------------------------------------------------------------------------# + def __pt_container__(self): + return self.dialog + diff --git a/jans-cli-tui/wui_components/jans_dialog_with_nav.py b/jans-cli-tui/wui_components/jans_dialog_with_nav.py new file mode 100644 index 00000000000..d068e9e931e --- /dev/null +++ b/jans-cli-tui/wui_components/jans_dialog_with_nav.py @@ -0,0 +1,57 @@ +from prompt_toolkit.layout.containers import ( + HSplit, + VSplit, + Window, +) +from prompt_toolkit.widgets import ( + Button, + Dialog, +) + +class JansDialogWithNav(): + def __init__(self,content,height=None,width=None,title=None, button_functions=[], navbar=None): + self.navbar = navbar + self.button_functions = button_functions + self.title = title + self.height = height + self.width =width + self.content = content + self.create_window() + + def create_window(self): + + max_data_str = 30 ## TODO TO BE Dynamic + + + self.dialog = Dialog( + title=self.title, + body=VSplit([ + HSplit( + [ + self.navbar + ], + + width= (max_data_str ) #self.width + ), + Window(width=1, char="|",), + HSplit([ + self.content + + + ]), + + + + ],width=120,height=32), + buttons=[ + Button( + text=str(self.button_functions[k][1]), + handler=self.button_functions[k][0], + ) for k in range(len(self.button_functions)) + ], + with_background=False, + ) +#--------------------------------------------------------------------------------------# + def __pt_container__(self): + return self.dialog + diff --git a/jans-cli-tui/wui_components/jans_nav_bar.py b/jans-cli-tui/wui_components/jans_nav_bar.py new file mode 100644 index 00000000000..e91cc2be899 --- /dev/null +++ b/jans-cli-tui/wui_components/jans_nav_bar.py @@ -0,0 +1,63 @@ +from prompt_toolkit.layout.containers import Window +from prompt_toolkit.layout.controls import FormattedTextControl +from prompt_toolkit.formatted_text import HTML, merge_formatted_text +from prompt_toolkit.key_binding import KeyBindings + + +class JansNavBar(): + def __init__(self, myparent, entries, selection_changed, select=0, bgcolor='#00ff44'): + self.myparent = myparent + self.navbar_entries = entries + self.cur_navbar_selection = select + self.bgcolor = bgcolor + self.selection_changed = selection_changed + self.cur_tab = entries[self.cur_navbar_selection][0] + self.create_window() + self.update_status_bar() + + + def create_window(self): + self.nav_window = Window( + content=FormattedTextControl( + text=self.get_navbar_entries, + focusable=True, + key_bindings=self.get_nav_bar_key_bindings(), + ), + height=1, + cursorline=False, + ) + + def get_navbar_entries(self): + + result = [] + for i, entry in enumerate(self.navbar_entries): + if i == self.cur_navbar_selection: + result.append(HTML(''.format(self.bgcolor, entry[1]))) + else: + result.append(HTML('{}'.format(entry[1]))) + result.append(" ") + + return merge_formatted_text(result) + + + def update_status_bar(self): + self.cur_tab = self.navbar_entries[self.cur_navbar_selection][0] + self.myparent.update_status_bar("Container for " + self.navbar_entries[self.cur_navbar_selection][1]) + if self.myparent.app_started: + self.selection_changed(self.cur_tab) + + + def get_nav_bar_key_bindings(self): + kb = KeyBindings() + + @kb.add("left") + def _go_up(event) -> None: + self.cur_navbar_selection = (self.cur_navbar_selection - 1) % len(self.navbar_entries) + self.update_status_bar() + + @kb.add("right") + def _go_up(event) -> None: + self.cur_navbar_selection = (self.cur_navbar_selection + 1) % len(self.navbar_entries) + self.update_status_bar() + + return kb diff --git a/jans-cli-tui/wui_components/jans_side_nav_bar.py b/jans-cli-tui/wui_components/jans_side_nav_bar.py new file mode 100644 index 00000000000..78da832d5b9 --- /dev/null +++ b/jans-cli-tui/wui_components/jans_side_nav_bar.py @@ -0,0 +1,87 @@ +from prompt_toolkit.layout.containers import ( + HSplit, + Window, + FloatContainer, + Dimension +) +from prompt_toolkit.formatted_text import merge_formatted_text +from prompt_toolkit.layout.controls import FormattedTextControl +from prompt_toolkit.key_binding import KeyBindings + + +class JansSideNavBar(): + def __init__(self, myparent, entries,selection_changed, select=0, entries_color='#00ff44'): + self.myparent = myparent # ListBox parent class + self.navbar_entries = entries # ListBox entries + self.cur_navbar_selection = select # ListBox initial selection + self.entries_color = entries_color + self.cur_tab = entries[self.cur_navbar_selection][0] + self.selection_changed = selection_changed + self.create_window() + self.update_status_bar() + + def create_window(self): + self.side_Nav = FloatContainer( + content=HSplit([ + Window( + content=FormattedTextControl( + text=self.get_navbar_entries, + focusable=True, + key_bindings=self.get_nav_bar_key_bindings(), + style=self.entries_color, + + ), + style="class:select-box", + height=Dimension(preferred=len( + self.navbar_entries)*2, max=len(self.navbar_entries)*2+1), + cursorline=True, + width= 10 #self.get_data_width() + ), + ] + ), floats=[] + ) +#--------------------------------------------------------------------------------------# + def get_data_width(self): + return len(max(self.navbar_entries, key=len)) +#--------------------------------------------------------------------------------------# + def get_navbar_entries(self): + + result = [] + for i, entry in enumerate(self.navbar_entries): + if i == self.cur_navbar_selection: + result.append([("[SetCursorPosition]", "")]) + result.append(entry) + result.append("\n") + result.append("\n") + return merge_formatted_text(result) +#--------------------------------------------------------------------------------------# + def update_status_bar(self): + self.cur_tab = self.navbar_entries[self.cur_navbar_selection] + self.myparent.update_status_bar( + "Container for " + self.navbar_entries[self.cur_navbar_selection]) + self.selection_changed(self.cur_tab) +#--------------------------------------------------------------------------------------# + + def get_nav_bar_key_bindings(self): + kb = KeyBindings() + + @kb.add("up") + def _go_up(event) -> None: + self.cur_navbar_selection = ( + self.cur_navbar_selection - 1) % len(self.navbar_entries) + self.update_status_bar() + + @kb.add("down") + def _go_up(event) -> None: + self.cur_navbar_selection = ( + self.cur_navbar_selection + 1) % len(self.navbar_entries) + self.update_status_bar() + + @kb.add("enter") + def _(event): + pass + + return kb + + def __pt_container__(self): + return self.side_Nav diff --git a/jans-cli-tui/wui_components/jans_vetrical_nav.py b/jans-cli-tui/wui_components/jans_vetrical_nav.py new file mode 100644 index 00000000000..845f384189d --- /dev/null +++ b/jans-cli-tui/wui_components/jans_vetrical_nav.py @@ -0,0 +1,197 @@ +from prompt_toolkit.widgets import TextArea +from prompt_toolkit.layout.containers import ( + HSplit, + Window, + FloatContainer, +) +from prompt_toolkit.layout.controls import FormattedTextControl +from prompt_toolkit.layout.margins import ScrollbarMargin +from prompt_toolkit.formatted_text import merge_formatted_text +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.layout.dimension import D + + +class JansVerticalNav(): + + def __init__(self, myparent, headers, selectes, on_enter, on_display, + all_data=None, preferred_size=[],data=None, headerColor='green', entriesColor='white'): + + self.myparent = myparent # ListBox parent class + self.headers = headers # ListBox headers + self.selectes = selectes # ListBox initial selection + # ListBox Data (Can be renderable ?!!! #TODO ) + self.data = data + self.preferred_size = preferred_size + self.headerColor = headerColor + self.entriesColor = entriesColor + self.spaces = [] + self.on_enter = on_enter + self.on_display = on_display + self.mod_data = data + self.all_data=all_data + self.view_data() + self.handel_Header_spaces() + self.handel_Data_spaces() + self.create_window() + + def view_data(self): + result = [] + # mod_data = [] + for i, entry in enumerate(self.mod_data): ## entry = ['1800.6c5faa', 'Jans Config Api Client', 'authorization_code,refresh_...', 'Reference] + mod_entry = [] + + for col in range(len(entry)) : + if self.preferred_size[col] == 0: + mod_entry.append(entry[col]) + else : + if self.preferred_size[col] >= len(entry[col]): + mod_entry.append(entry[col]) + else : + mod_entry.append(entry[col][:self.preferred_size[col]]+'...') + + result.append(mod_entry) + + self.mod_data = result + + def create_window(self): + self.ClientID = TextArea(height=1, multiline=False) + self.ClientName = TextArea(height=1, multiline=False) + self.GrantType = TextArea(height=1, multiline=False) + self.AccessToken = TextArea(height=1, multiline=False) + self.Helper = TextArea(height=1, multiline=False, focusable=False) + + if self.data == None: + self.data = [['No Cliend ID', 'No Client Name', + 'No Grant Type', 'No Access Token']] + + + self.container = FloatContainer( + content=HSplit([ + Window( + content=FormattedTextControl( + text=self._get_head_text, + focusable=False, + key_bindings=self._get_key_bindings(), + style=self.headerColor, + ), + style="class:select-box", + height=D(preferred=1, max=1), + cursorline=False, + ), + Window(height=1), + Window( + content=FormattedTextControl( + text=self._get_formatted_text, + focusable=True, + key_bindings=self._get_key_bindings(), + style=self.entriesColor, + ), + style="class:select-box", + height=D(preferred=len(self.data), max=len(self.data)), + cursorline=True, + right_margins=[ScrollbarMargin(display_arrows=True), ], + ), + Window(height=10), + ] + ), + + floats=[ + ], + ) + + def handel_Header_spaces(self): + datalen = [] + for dataline in range(len(self.mod_data )): + line = [] + for i in self.mod_data[dataline]: + line.append(len(i)) + datalen.append(line) + dict = {} + for num in range(len(datalen[0])): + dict[num] = [] + + for k in range(len(datalen)): + for i in range(len(datalen[k])): + dict[i].append(datalen[k][i]) + + for i in dict: + self.spaces.append(max(dict[i])) + + for i in range(len(self.spaces)): ## handel header collesion (when the headers length is greater that the tallest data index length) + + if self.spaces[i] < len(self.headers[i]): + self.spaces[i] = self.spaces[i] + \ + (len(self.headers[i]) - self.spaces[i]) + + self.spaces[-1] = self.myparent.output.get_size()[1] - sum(self.spaces) + sum(len(s) for s in self.headers) ## handel last head spaces (add space to the end of ter. width to remove the white line) + # -------------------------------------------------------------------------------- # + # -------------------------------------------------------------------------------- # + def handel_Data_spaces(self): + for i in range(len(self.mod_data)): + for k in range(len(self.spaces)): + if len(self.mod_data[i][k]) != self.spaces[k]: + self.mod_data[i][k] = ( + self.mod_data[i][k] + " " * (self.spaces[k] - len(self.mod_data[i][k]))) + else: + pass + + def _get_head_text(self): + result = [] + y = '' + for k in range(len(self.headers)): + y += self.headers[k] + ' ' * \ + (self.spaces[k] - len(self.headers[k]) + 5) + result.append(y) + result.append("\n") + + return merge_formatted_text(result) + + def _get_formatted_text(self): + result = [] + for i, entry in enumerate(self.mod_data): ## entry = ['1800.6c5faa', 'Jans Config Api Client', 'authorization_code,refresh_...', 'Reference] + if i == self.selectes: + result.append([("[SetCursorPosition]", "")]) + + result.append(' '.join(entry)) + result.append("\n") + + return merge_formatted_text(result) + + # -------------------------------------------------------------------------------- # + # -------------------------------------------------------------------------------- # + def _get_key_bindings(self): + kb = KeyBindings() + + @kb.add("up") + def _go_up(event) -> None: + self.selectes = (self.selectes - 1) % len(self.data) + + @kb.add("down") + def _go_up(event) -> None: + self.selectes = (self.selectes + 1) % len(self.data) + + @kb.add("enter") + def _(event): + passed = [i.strip() for i in self.data[self.selectes]] + size = self.myparent.output.get_size() + self.on_enter(passed,event,size) + + + @kb.add("d") + def _(event): + selected_line = [i.strip() for i in self.data[self.selectes]] + size = self.myparent.output.get_size() + self.on_display( + selected=selected_line, + headers=self.headers, + event=event, + size=size, + data=self.all_data[self.selectes]) + + return kb + + # -------------------------------------------------------------------------------- # + # -------------------------------------------------------------------------------- # + + def __pt_container__(self): + return self.container From 630fd320d712969d521dfdf7c06b99265f1f8648 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Thu, 11 Aug 2022 22:21:37 +0300 Subject: [PATCH 002/364] fix: jans-cli focus after closing dialog --- jans-cli-tui/jans-cli-tui.py | 1 - 1 file changed, 1 deletion(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index e9fa9d4fd4e..cf7cc8f0951 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -316,7 +316,6 @@ def save_creds(self, dialog): def show_jans_dialog(self, dialog): async def coroutine(): - self.layout.focus(dialog) result = await self.show_dialog_as_float(dialog) return result ensure_future(coroutine()) From 0242772b39b08192f22ece69837718dbadd42cba Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Thu, 11 Aug 2022 22:41:15 +0300 Subject: [PATCH 003/364] feat: jans-cli check connection at startup --- jans-cli-tui/cli/config_cli.py | 8 +++++++- jans-cli-tui/jans-cli-tui.py | 16 +++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/jans-cli-tui/cli/config_cli.py b/jans-cli-tui/cli/config_cli.py index 802367de006..4dcba1234f4 100755 --- a/jans-cli-tui/cli/config_cli.py +++ b/jans-cli-tui/cli/config_cli.py @@ -429,9 +429,12 @@ def check_connection(self): verify=self.verify_ssl, cert=self.mtls_client_cert ) - + self.log_response(response) if response.status_code != 200: + if self.wrapped: + return response.text + raise ValueError( self.colored_text("Unable to connect jans-auth server:\n {}".format(response.text), error_color)) @@ -445,6 +448,9 @@ def check_connection(self): if not response.status_code == 200: self.access_token = None + return response.text + + return True def check_access_token(self): diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index cf7cc8f0951..33456a809b8 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -101,6 +101,7 @@ def __init__(self): self.check_jans_cli_ini() # ----------------------------------------------------------------------------- # + self.yes_button = Button(text="Yes", handler=accept_yes) self.no_button = Button(text="No", handler=accept_no) self.status_bar = TextArea(style="class:status", height=1, focusable=False) @@ -169,12 +170,21 @@ def create_cli(self): wrapped=True ) + + status = self.cli_object.check_connection() + + if status is True: + return + + self.show_message("Error getting Access Token", status) + def check_jans_cli_ini(self): if not(config_cli.host and (config_cli.client_id and config_cli.client_secret or config_cli.access_token)): self.jans_creds_dialog() else : self.create_cli() + def dialog_back_but(self): ## BACK self.active_dialog_select = '' self.show_dialog = False @@ -342,8 +352,12 @@ def edit_scope_dialog(self, **params): dialog = EditScopeDialog(self,**params) self.show_jans_dialog(dialog) + def show_message(self, title, message): + body = HSplit([Label(message)]) + dialog = JansGDialog(title=title, body=body) + self.show_jans_dialog(dialog) - + application = JansCliApp() def run(): From d05ca664334b3ced51aba18a12e882d616422786 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Fri, 12 Aug 2022 10:03:12 +0300 Subject: [PATCH 004/364] fix: jans-cli focus before and after dialog --- jans-cli-tui/jans-cli-tui.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index 33456a809b8..88c095790eb 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -176,7 +176,8 @@ def create_cli(self): if status is True: return - self.show_message("Error getting Access Token", status) + buttons = [Button("Exit", handler=do_exit)] + self.show_message("Error getting Access Token", status)#, buttons=buttons) def check_jans_cli_ini(self): if not(config_cli.host and (config_cli.client_id and config_cli.client_secret or config_cli.access_token)): @@ -280,18 +281,21 @@ async def show_dialog_as_float(self, dialog): float_ = Float(content=dialog) self.root_layout.floats.insert(0, float_) - app = get_app() - - focused_before = app.layout.current_window - app.layout.focus(dialog) result = await dialog.future - app.layout.focus(focused_before) if float_ in self.root_layout.floats: self.root_layout.floats.remove(float_) return result + def show_jans_dialog(self, dialog): + async def coroutine(): + focused_before = self.layout.current_window + self.layout.focus(dialog) + result = await self.show_dialog_as_float(dialog) + self.layout.focus(focused_before) + return result + ensure_future(coroutine()) def data_display_dialog(self, **params): @@ -324,11 +328,6 @@ def save_creds(self, dialog): self.create_cli() - def show_jans_dialog(self, dialog): - async def coroutine(): - result = await self.show_dialog_as_float(dialog) - return result - ensure_future(coroutine()) def jans_creds_dialog(self): @@ -352,9 +351,9 @@ def edit_scope_dialog(self, **params): dialog = EditScopeDialog(self,**params) self.show_jans_dialog(dialog) - def show_message(self, title, message): + def show_message(self, title, message, buttons=[]): body = HSplit([Label(message)]) - dialog = JansGDialog(title=title, body=body) + dialog = JansGDialog(title=title, body=body, buttons=buttons) self.show_jans_dialog(dialog) From 30da75eb4badb1e34ccffa04cde495c137114e4a Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Fri, 12 Aug 2022 10:47:07 +0300 Subject: [PATCH 005/364] fix: jans-cli check client creds --- jans-cli-tui/cli/config_cli.py | 27 ++++++++++++++++++--------- jans-cli-tui/jans-cli-tui.py | 31 +++++++++++++++++-------------- 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/jans-cli-tui/cli/config_cli.py b/jans-cli-tui/cli/config_cli.py index 4dcba1234f4..8cd500e26a3 100755 --- a/jans-cli-tui/cli/config_cli.py +++ b/jans-cli-tui/cli/config_cli.py @@ -291,13 +291,13 @@ def __contains__(self, child): class JCA_CLI: - def __init__(self, host, client_id, client_secret, access_token, test_client=False, wrapped=False): + def __init__(self, host, client_id, client_secret, access_token, test_client=False): self.host = self.idp_host = host self.client_id = client_id self.client_secret = client_secret self.use_test_client = test_client self.getCredintials() - self.wrapped = wrapped + self.wrapped = __name__ != "__main__" self.access_token = access_token or config['DEFAULT'].get('access_token') self.jwt_validation_url = 'https://{}/jans-config-api/api/v1/acrs'.format(self.idp_host) self.set_user() @@ -422,13 +422,22 @@ def get_request_header(self, headers={}, access_token=None): def check_connection(self): url = 'https://{}/jans-auth/restv1/token'.format(self.idp_host) - response = requests.post( - url=url, - auth=(self.client_id, self.client_secret), - data={"grant_type": "client_credentials"}, - verify=self.verify_ssl, - cert=self.mtls_client_cert - ) + try: + + response = requests.post( + url=url, + auth=(self.client_id, self.client_secret), + data={"grant_type": "client_credentials"}, + verify=self.verify_ssl, + cert=self.mtls_client_cert + ) + except Exception as e: + if self.wrapped: + return str(e) + + raise ValueError( + self.colored_text("Unable to connect jans-auth server:\n {}".format(str(e)), error_color)) + self.log_response(response) if response.status_code != 200: diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index 88c095790eb..fbf0f50a137 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -158,7 +158,7 @@ def __init__(self): # Since first module is oauth, set center frame to my oauth main container. self.oauth_set_center_frame() - + def create_cli(self): test_client = config_cli.client_id if config_cli.test_client else None self.cli_object = config_cli.JCA_CLI( @@ -166,18 +166,17 @@ def create_cli(self): client_id=config_cli.client_id, client_secret=config_cli.client_secret, access_token=config_cli.access_token, - test_client=test_client, - wrapped=True + test_client=test_client ) - + open("/tmp/cc.txt", "a").write("Checking connection\n") status = self.cli_object.check_connection() if status is True: return - buttons = [Button("Exit", handler=do_exit)] - self.show_message("Error getting Access Token", status)#, buttons=buttons) + buttons = [Button("OK", handler=self.jans_creds_dialog)] + self.show_message("Error getting Access Token", status, buttons=buttons) def check_jans_cli_ini(self): if not(config_cli.host and (config_cli.client_id and config_cli.client_secret or config_cli.access_token)): @@ -213,9 +212,9 @@ def set_keybindings(self): self.bindings.add("s-tab")(self.focus_previous) self.bindings.add("c-c")(do_exit) - def getTitledText(self, title, name, height=1, jans_help='', width=None): + def getTitledText(self, title, name, value='', height=1, jans_help='', width=None): multiline = height > 1 - ta = TextArea(multiline=multiline, style='class:textarea') + ta = TextArea(text=value, multiline=multiline, style='class:textarea') ta.window.jans_name = name ta.window.jans_help = jans_help if width: @@ -290,10 +289,14 @@ async def show_dialog_as_float(self, dialog): def show_jans_dialog(self, dialog): async def coroutine(): - focused_before = self.layout.current_window + app = get_app() + focused_before = app.layout.current_window self.layout.focus(dialog) result = await self.show_dialog_as_float(dialog) - self.layout.focus(focused_before) + try: + app.layout.focus(focused_before) + except: + app.layout.focus(self.center_frame) return result ensure_future(coroutine()) @@ -329,12 +332,12 @@ def save_creds(self, dialog): self.create_cli() - def jans_creds_dialog(self): + def jans_creds_dialog(self, *params): body=HSplit([ - self.getTitledText("Hostname", name='jans_host', jans_help="FQN name of Jannsen Config Api Server"), - self.getTitledText("Client ID", name='jca_client_id', jans_help="Jannsen Config Api Client ID"), - self.getTitledText("Client Secret", name='jca_client_secret', jans_help="Jannsen Config Api Client Secret") + self.getTitledText("Hostname", name='jans_host', value=config_cli.host or '', jans_help="FQN name of Jannsen Config Api Server"), + self.getTitledText("Client ID", name='jca_client_id', value=config_cli.client_id or '', jans_help="Jannsen Config Api Client ID"), + self.getTitledText("Client Secret", name='jca_client_secret', value=config_cli.client_secret or '', jans_help="Jannsen Config Api Client Secret") ]) buttons = [Button("Save", handler=self.save_creds)] From ec6472ba36560ba8dde902499dcbb54da05eea48 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Fri, 12 Aug 2022 14:42:38 +0300 Subject: [PATCH 006/364] fix: jans-cli auth-token flow --- jans-cli-tui/cli/config_cli.py | 58 ++++++++++++++++++++-------------- jans-cli-tui/jans-cli-tui.py | 23 +++++++++++--- jans-cli-tui/wrapper_test.py | 25 +++++++++++++++ 3 files changed, 78 insertions(+), 28 deletions(-) create mode 100644 jans-cli-tui/wrapper_test.py diff --git a/jans-cli-tui/cli/config_cli.py b/jans-cli-tui/cli/config_cli.py index 8cd500e26a3..4214670cc40 100755 --- a/jans-cli-tui/cli/config_cli.py +++ b/jans-cli-tui/cli/config_cli.py @@ -641,15 +641,7 @@ def get_device_authorization (self): return response.json() - def get_jwt_access_token(self): - - - """ - STEP 1: Get device verification code - This fucntion requests user code from jans-auth, print result and - waits untill verification done. - """ - + def get_device_verification_code(self): response = requests.post( url='https://{}/jans-auth/restv1/device_authorization'.format(self.host), auth=(self.client_id, self.client_secret), @@ -657,27 +649,45 @@ def get_jwt_access_token(self): verify=self.verify_ssl, cert=self.mtls_client_cert ) + self.log_response(response) - if response.status_code != 200: - raise ValueError( - self.colored_text("Unable to get device authorization user code: {}".format(response.reason), error_color)) - result = response.json() + return response - if 'verification_uri' in result and 'user_code' in result: - print("Please visit verification url {} and enter user code {} in {} secods".format( - self.colored_text(result['verification_uri'], success_color), - self.colored_text(result['user_code'], bold_color), - result['expires_in'] - ) - ) + def get_jwt_access_token(self, device_verified=None): - input(self.colored_text("Please press «Enter» when ready", warning_color)) - else: - raise ValueError(self.colored_text("Unable to get device authorization user code")) + """ + STEP 1: Get device verification code + This fucntion requests user code from jans-auth, print result and + waits untill verification done. + """ + if not device_verified: + response = self.get_device_verification_code() + if response.status_code != 200: + msg = "Unable to get device authorization user code: {}".format(response.reason) + raise ValueError( + self.colored_text(msg, error_color)) + result = response.json() + + if 'verification_uri' in result and 'user_code' in result: + + msg = "Please visit verification url {} and enter user code {} in {} secods".format( + self.colored_text(result['verification_uri'], success_color), + self.colored_text(result['user_code'], bold_color), + result['expires_in'] + ) + print(msg) + input(self.colored_text("Please press «Enter» when ready", warning_color)) + + else: + msg = "Unable to get device authorization user code" + raise ValueError(self.colored_text(msg)) + + else: + result = device_verified """ STEP 2: Get access token for retreiving user info @@ -748,6 +758,7 @@ def get_jwt_access_token(self): config['DEFAULT']['access_token_enc'] = access_token_enc write_config() + return True, '' def get_access_token(self, scope): if self.use_test_client: @@ -755,6 +766,7 @@ def get_access_token(self, scope): elif not self.access_token: self.check_access_token() self.get_jwt_access_token() + return True, '' def print_exception(self, e): error_printed = False diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index fbf0f50a137..d138e8cc3ff 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -169,14 +169,27 @@ def create_cli(self): test_client=test_client ) - open("/tmp/cc.txt", "a").write("Checking connection\n") status = self.cli_object.check_connection() - if status is True: - return + if status is not True: + buttons = [Button("OK", handler=self.jans_creds_dialog)] + self.show_message("Error getting Connection Config Api", status, buttons=buttons) + else: + if not test_client and not self.cli_object.access_token: + try: + response = self.cli_object.get_device_verification_code() + result = response.json() + + msg = "Please visit verification url {} and enter user code {} in {} secods".format( + result['verification_uri'], result['user_code'], result['expires_in'] + ) + + self.show_message("Waiting Response", msg) + self.cli_object.get_jwt_access_token(result) + + except Exception as e: + self.show_message("ERROR", "An Error ocurred while getting device authorization code: " + str(e)) - buttons = [Button("OK", handler=self.jans_creds_dialog)] - self.show_message("Error getting Access Token", status, buttons=buttons) def check_jans_cli_ini(self): if not(config_cli.host and (config_cli.client_id and config_cli.client_secret or config_cli.access_token)): diff --git a/jans-cli-tui/wrapper_test.py b/jans-cli-tui/wrapper_test.py new file mode 100644 index 00000000000..8d1488d5e0c --- /dev/null +++ b/jans-cli-tui/wrapper_test.py @@ -0,0 +1,25 @@ +from cli import config_cli +test_client = config_cli.client_id if config_cli.test_client else None +cli_object = config_cli.JCA_CLI( + host=config_cli.host, + client_id=config_cli.client_id, + client_secret=config_cli.client_secret, + access_token=config_cli.access_token, + test_client=test_client + ) + +print(config_cli.host, config_cli.client_id, config_cli.client_secret, config_cli.access_token, cli_object.use_test_client) +status = cli_object.check_connection() + +print(status) + +response = cli_object.get_device_verification_code() + +result = response.json() +print(result) + +input() + +cli_object.get_jwt_access_token(result) + +print(result) From 9d988631f2101248c9eef28cafc2dc07c1b79b3c Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Mon, 15 Aug 2022 14:38:20 +0300 Subject: [PATCH 007/364] fix: jans-cli device auth flow --- jans-cli-tui/jans-cli-tui.py | 53 +++++++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index d138e8cc3ff..1b54f4f9fb4 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -176,7 +176,7 @@ def create_cli(self): self.show_message("Error getting Connection Config Api", status, buttons=buttons) else: if not test_client and not self.cli_object.access_token: - try: + response = self.cli_object.get_device_verification_code() result = response.json() @@ -184,11 +184,22 @@ def create_cli(self): result['verification_uri'], result['user_code'], result['expires_in'] ) - self.show_message("Waiting Response", msg) - self.cli_object.get_jwt_access_token(result) + body = HSplit([Label(msg)]) + dialog = JansGDialog(title="Waiting Response", body=body) + + async def coroutine(): + app = get_app() + focused_before = app.layout.current_window + self.layout.focus(dialog) + await self.show_dialog_as_float(dialog) + try: + app.layout.focus(focused_before) + except: + app.layout.focus(self.center_frame) - except Exception as e: - self.show_message("ERROR", "An Error ocurred while getting device authorization code: " + str(e)) + self.cli_object.get_jwt_access_token(result) + + ensure_future(coroutine()) def check_jans_cli_ini(self): @@ -291,16 +302,22 @@ def main_nav_selection_changed(self, selection): async def show_dialog_as_float(self, dialog): "Coroutine." float_ = Float(content=dialog) - self.root_layout.floats.insert(0, float_) + self.root_layout.floats.append(float_) result = await dialog.future if float_ in self.root_layout.floats: self.root_layout.floats.remove(float_) + if self.root_layout.floats: + self.layout.focus(self.root_layout.floats[-1].content) + else: + self.layout.focus(self.center_frame) + return result def show_jans_dialog(self, dialog): + async def coroutine(): app = get_app() focused_before = app.layout.current_window @@ -310,8 +327,13 @@ async def coroutine(): app.layout.focus(focused_before) except: app.layout.focus(self.center_frame) + + return result + ensure_future(coroutine()) + + def data_display_dialog(self, **params): @@ -342,8 +364,6 @@ def save_creds(self, dialog): config_cli.client_id = config_cli.config['DEFAULT']['jca_client_id'] config_cli.client_secret = config_cli.config['DEFAULT']['jca_client_secret'] - self.create_cli() - def jans_creds_dialog(self, *params): @@ -356,8 +376,21 @@ def jans_creds_dialog(self, *params): buttons = [Button("Save", handler=self.save_creds)] dialog = JansGDialog(title="Janssen Config Api Client Credidentials", body=body, buttons=buttons) - self.show_jans_dialog(dialog) - + async def coroutine(): + app = get_app() + focused_before = app.layout.current_window + self.layout.focus(dialog) + result = await self.show_dialog_as_float(dialog) + try: + app.layout.focus(focused_before) + except: + app.layout.focus(self.center_frame) + + self.create_cli() + + ensure_future(coroutine()) + + def edit_client_dialog(self, **params): dialog = EditClientDialog(self,**params) From a473ecefedc75c3737013e8441c466e3c4efe08f Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Tue, 16 Aug 2022 12:10:52 +0300 Subject: [PATCH 008/364] fix: jans-cli simulate tab keypress to fire dialog --- jans-cli-tui/jans-cli-tui.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index 1b54f4f9fb4..dffca378aa5 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -6,6 +6,7 @@ from shutil import get_terminal_size import time from asyncio import Future, ensure_future +from pynput.keyboard import Key, Controller import prompt_toolkit from prompt_toolkit.application import Application @@ -97,10 +98,7 @@ def __init__(self): self.active_dialog_select = '' self.Auth_clients_tabs = {} - # ----------------------------------------------------------------------------- # - self.check_jans_cli_ini() - # ----------------------------------------------------------------------------- # - + self.keyboard = Controller() self.yes_button = Button(text="Yes", handler=accept_yes) self.no_button = Button(text="No", handler=accept_no) @@ -159,6 +157,11 @@ def __init__(self): # Since first module is oauth, set center frame to my oauth main container. self.oauth_set_center_frame() + + # ----------------------------------------------------------------------------- # + self.check_jans_cli_ini() + # ----------------------------------------------------------------------------- # + def create_cli(self): test_client = config_cli.client_id if config_cli.test_client else None self.cli_object = config_cli.JCA_CLI( @@ -171,6 +174,9 @@ def create_cli(self): status = self.cli_object.check_connection() + self.keyboard.press(Key.tab) + self.keyboard.release(Key.tab) + if status is not True: buttons = [Button("OK", handler=self.jans_creds_dialog)] self.show_message("Error getting Connection Config Api", status, buttons=buttons) From 53d04106e46cb176e3554e9392904a8b808d2139 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Tue, 16 Aug 2022 12:49:43 +0300 Subject: [PATCH 009/364] fix: jans-cli error checking in device auth flow --- jans-cli-tui/cli/config_cli.py | 20 +++++++++++--------- jans-cli-tui/jans-cli-tui.py | 21 ++++++++++++++++----- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/jans-cli-tui/cli/config_cli.py b/jans-cli-tui/cli/config_cli.py index 4214670cc40..ce5f0c8db97 100755 --- a/jans-cli-tui/cli/config_cli.py +++ b/jans-cli-tui/cli/config_cli.py @@ -655,6 +655,12 @@ def get_device_verification_code(self): return response + def raise_error(self, msg): + if not self.wrapped: + msg = self.colored_text(msg, error_color) + raise ValueError(msg) + + def get_jwt_access_token(self, device_verified=None): @@ -667,8 +673,7 @@ def get_jwt_access_token(self, device_verified=None): response = self.get_device_verification_code() if response.status_code != 200: msg = "Unable to get device authorization user code: {}".format(response.reason) - raise ValueError( - self.colored_text(msg, error_color)) + self.raise_error(msg) result = response.json() @@ -684,7 +689,7 @@ def get_jwt_access_token(self, device_verified=None): else: msg = "Unable to get device authorization user code" - raise ValueError(self.colored_text(msg)) + self.raise_error(msg) else: result = device_verified @@ -708,8 +713,7 @@ def get_jwt_access_token(self, device_verified=None): ) self.log_response(response) if response.status_code != 200: - raise ValueError( - self.colored_text("Unable to get access token")) + self.raise_error("Unable to get access token") result = response.json() @@ -728,8 +732,7 @@ def get_jwt_access_token(self, device_verified=None): ) self.log_response(response) if response.status_code != 200: - raise ValueError( - self.colored_text("Unable to get access token")) + self.raise_error("Unable to get access token") result = response.text @@ -748,8 +751,7 @@ def get_jwt_access_token(self, device_verified=None): ) self.log_response(response) if response.status_code != 200: - raise ValueError( - self.colored_text("Unable to get access token")) + self.raise_error("Unable to get access token") result = response.json() diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index dffca378aa5..669a5e68be3 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -162,7 +162,13 @@ def __init__(self): self.check_jans_cli_ini() # ----------------------------------------------------------------------------- # + + def press_tab(self): + self.keyboard.press(Key.tab) + self.keyboard.release(Key.tab) + def create_cli(self): + conn_ok = False test_client = config_cli.client_id if config_cli.test_client else None self.cli_object = config_cli.JCA_CLI( host=config_cli.host, @@ -174,15 +180,14 @@ def create_cli(self): status = self.cli_object.check_connection() - self.keyboard.press(Key.tab) - self.keyboard.release(Key.tab) + self.press_tab() if status is not True: buttons = [Button("OK", handler=self.jans_creds_dialog)] self.show_message("Error getting Connection Config Api", status, buttons=buttons) else: if not test_client and not self.cli_object.access_token: - + response = self.cli_object.get_device_verification_code() result = response.json() @@ -202,11 +207,17 @@ async def coroutine(): app.layout.focus(focused_before) except: app.layout.focus(self.center_frame) - - self.cli_object.get_jwt_access_token(result) + try: + self.cli_object.get_jwt_access_token(result) + except Exception as e: + err_dialog = JansGDialog(title="Error!", body=HSplit([Label(str(e))])) + await self.show_dialog_as_float(err_dialog) + self.create_cli() ensure_future(coroutine()) + #if not conn_ok: + # self.create_cli() def check_jans_cli_ini(self): if not(config_cli.host and (config_cli.client_id and config_cli.client_secret or config_cli.access_token)): From 939af86eac58ae4bd6ffc1c530e12397ab8099ed Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Tue, 16 Aug 2022 06:07:56 -0700 Subject: [PATCH 010/364] hopa --- jans-cli-tui/jans-cli-tui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index 669a5e68be3..d722f52ff25 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -89,7 +89,7 @@ def __init__(self): self.app_started = False self.width, self.height = get_terminal_size() self.app = get_app() - self.show_dialog = False ## ## ## ## + self.show_dialog = False self.set_keybindings() self.containers = {} # -------------------------------------------------------------------------------- # From 4ee20dcf74c3f2bf5b30c72d6efd5b7d4643ed3a Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Tue, 16 Aug 2022 06:10:10 -0700 Subject: [PATCH 011/364] hopa(16-8-2022) --- jans-cli-tui/cli/config_cli.py | 20 +- jans-cli-tui/cli/jca.yaml | 0 jans-cli-tui/cli/pylib/__init__.py | 0 jans-cli-tui/cli/pylib/tabulate/LICENSE | 0 jans-cli-tui/cli/pylib/tabulate/README | 0 jans-cli-tui/cli/pylib/tabulate/README.md | 0 jans-cli-tui/cli/pylib/tabulate/__init__.py | 0 jans-cli-tui/cli/pylib/tabulate/tabulate.py | 0 jans-cli-tui/cli/swagger_yaml.json | 0 jans-cli-tui/cli_style.py | 2 +- jans-cli-tui/depug_hopa2.txt | 108 +++++++++ jans-cli-tui/jans-cli-tui.py | 214 ++++++++++++----- jans-cli-tui/models/oauth.py | 226 +++++++++++++++--- jans-cli-tui/static.py | 0 jans-cli-tui/test_logstring.py | 73 ++++++ jans-cli-tui/wrapper_test.py | 0 .../wui_components/edit_client_dialog.py | 1 - .../wui_components/edit_scope_dialog.py | 1 - .../wui_components/jans_cli_dialog.py | 23 +- jans-cli-tui/wui_components/jans_dialog.py | 1 - .../wui_components/jans_dialog_with_nav.py | 1 - jans-cli-tui/wui_components/jans_nav_bar.py | 2 +- .../wui_components/jans_side_nav_bar.py | 2 +- .../wui_components/jans_vetrical_nav.py | 15 +- 24 files changed, 552 insertions(+), 137 deletions(-) mode change 100644 => 100755 jans-cli-tui/cli/jca.yaml mode change 100644 => 100755 jans-cli-tui/cli/pylib/__init__.py mode change 100644 => 100755 jans-cli-tui/cli/pylib/tabulate/LICENSE mode change 100644 => 100755 jans-cli-tui/cli/pylib/tabulate/README mode change 100644 => 100755 jans-cli-tui/cli/pylib/tabulate/README.md mode change 100644 => 100755 jans-cli-tui/cli/pylib/tabulate/__init__.py mode change 100644 => 100755 jans-cli-tui/cli/pylib/tabulate/tabulate.py mode change 100644 => 100755 jans-cli-tui/cli/swagger_yaml.json mode change 100644 => 100755 jans-cli-tui/cli_style.py create mode 100644 jans-cli-tui/depug_hopa2.txt mode change 100644 => 100755 jans-cli-tui/models/oauth.py mode change 100644 => 100755 jans-cli-tui/static.py create mode 100644 jans-cli-tui/test_logstring.py mode change 100644 => 100755 jans-cli-tui/wrapper_test.py mode change 100644 => 100755 jans-cli-tui/wui_components/edit_client_dialog.py mode change 100644 => 100755 jans-cli-tui/wui_components/edit_scope_dialog.py mode change 100644 => 100755 jans-cli-tui/wui_components/jans_cli_dialog.py mode change 100644 => 100755 jans-cli-tui/wui_components/jans_dialog.py mode change 100644 => 100755 jans-cli-tui/wui_components/jans_dialog_with_nav.py mode change 100644 => 100755 jans-cli-tui/wui_components/jans_nav_bar.py mode change 100644 => 100755 jans-cli-tui/wui_components/jans_side_nav_bar.py mode change 100644 => 100755 jans-cli-tui/wui_components/jans_vetrical_nav.py diff --git a/jans-cli-tui/cli/config_cli.py b/jans-cli-tui/cli/config_cli.py index ce5f0c8db97..4214670cc40 100755 --- a/jans-cli-tui/cli/config_cli.py +++ b/jans-cli-tui/cli/config_cli.py @@ -655,12 +655,6 @@ def get_device_verification_code(self): return response - def raise_error(self, msg): - if not self.wrapped: - msg = self.colored_text(msg, error_color) - raise ValueError(msg) - - def get_jwt_access_token(self, device_verified=None): @@ -673,7 +667,8 @@ def get_jwt_access_token(self, device_verified=None): response = self.get_device_verification_code() if response.status_code != 200: msg = "Unable to get device authorization user code: {}".format(response.reason) - self.raise_error(msg) + raise ValueError( + self.colored_text(msg, error_color)) result = response.json() @@ -689,7 +684,7 @@ def get_jwt_access_token(self, device_verified=None): else: msg = "Unable to get device authorization user code" - self.raise_error(msg) + raise ValueError(self.colored_text(msg)) else: result = device_verified @@ -713,7 +708,8 @@ def get_jwt_access_token(self, device_verified=None): ) self.log_response(response) if response.status_code != 200: - self.raise_error("Unable to get access token") + raise ValueError( + self.colored_text("Unable to get access token")) result = response.json() @@ -732,7 +728,8 @@ def get_jwt_access_token(self, device_verified=None): ) self.log_response(response) if response.status_code != 200: - self.raise_error("Unable to get access token") + raise ValueError( + self.colored_text("Unable to get access token")) result = response.text @@ -751,7 +748,8 @@ def get_jwt_access_token(self, device_verified=None): ) self.log_response(response) if response.status_code != 200: - self.raise_error("Unable to get access token") + raise ValueError( + self.colored_text("Unable to get access token")) result = response.json() diff --git a/jans-cli-tui/cli/jca.yaml b/jans-cli-tui/cli/jca.yaml old mode 100644 new mode 100755 diff --git a/jans-cli-tui/cli/pylib/__init__.py b/jans-cli-tui/cli/pylib/__init__.py old mode 100644 new mode 100755 diff --git a/jans-cli-tui/cli/pylib/tabulate/LICENSE b/jans-cli-tui/cli/pylib/tabulate/LICENSE old mode 100644 new mode 100755 diff --git a/jans-cli-tui/cli/pylib/tabulate/README b/jans-cli-tui/cli/pylib/tabulate/README old mode 100644 new mode 100755 diff --git a/jans-cli-tui/cli/pylib/tabulate/README.md b/jans-cli-tui/cli/pylib/tabulate/README.md old mode 100644 new mode 100755 diff --git a/jans-cli-tui/cli/pylib/tabulate/__init__.py b/jans-cli-tui/cli/pylib/tabulate/__init__.py old mode 100644 new mode 100755 diff --git a/jans-cli-tui/cli/pylib/tabulate/tabulate.py b/jans-cli-tui/cli/pylib/tabulate/tabulate.py old mode 100644 new mode 100755 diff --git a/jans-cli-tui/cli/swagger_yaml.json b/jans-cli-tui/cli/swagger_yaml.json old mode 100644 new mode 100755 diff --git a/jans-cli-tui/cli_style.py b/jans-cli-tui/cli_style.py old mode 100644 new mode 100755 index d1ec76f0376..ab728d6ec61 --- a/jans-cli-tui/cli_style.py +++ b/jans-cli-tui/cli_style.py @@ -18,4 +18,4 @@ "textarea":"nounderline bg:#ffffff fg:#d1c0c0 #ff0000", "checkbox":"nounderline bg:#ffffff fg:#d1c0c0 #ff0000", } -) +) \ No newline at end of file diff --git a/jans-cli-tui/depug_hopa2.txt b/jans-cli-tui/depug_hopa2.txt new file mode 100644 index 00000000000..51e44947a25 --- /dev/null +++ b/jans-cli-tui/depug_hopa2.txt @@ -0,0 +1,108 @@ + + + +{'dn': 'inum=1800.6c5faab4-dc28-4d46-94f4-df320f7c9780, +ou=clients,o=jans', + 'deletable': False, + 'clientSecret': 'oVSD1QqiLWRW', + 'frontChannelLogoutSessionRequired': False, + 'redirectUris': ['https://c1.gluu.org/admin-ui', 'http://localhost:4100'], + 'responseTypes': ['code'], + 'grantTypes': ['authorization_code', 'refresh_token', 'client_credentials'], + 'applicationType': 'web', + 'clientName': {'values': {'': 'Jans Config Api Client'}, + 'languageTags': [''], + 'value': 'Jans Config Api Client'}, + 'logoUri': {}, + 'clientUri': {}, + 'policyUri': {}, + 'tosUri': {}, + 'subjectType': 'pairwise', + 'idTokenSignedResponseAlg': 'RS256', + 'tokenEndpointAuthMethod': 'client_secret_basic', + 'requireAuthTime': False, + 'scopes': ['inum=C4F7,ou=scopes,o=jans', + 'inum=1200.1F13DB,ou=scopes,o=jans', + 'inum=1200.E0B4C8,ou=scopes,o=jans', + 'inum=1800.590346,ou=scopes,o=jans', + 'inum=1800.507006,ou=scopes,o=jans', + 'inum=1800.7866C7,ou=scopes,o=jans', + 'inum=1800.A1D5CD,ou=scopes,o=jans', + 'inum=1800.0714D9,ou=scopes,o=jans', + 'inum=1800.32C574,ou=scopes,o=jans', + 'inum=1800.C1E748,ou=scopes,o=jans', + 'inum=1800.19E881,ou=scopes,o=jans', + 'inum=1800.913FF3,ou=scopes,o=jans', + 'inum=1800.327D46,ou=scopes,o=jans', + 'inum=1800.99C0D3,ou=scopes,o=jans', + 'inum=1800.BBD836,ou=scopes,o=jans', + 'inum=1800.F8AAB3,ou=scopes,o=jans', + 'inum=1800.E03CFE,ou=scopes,o=jans', + 'inum=1800.7DC04D,ou=scopes,o=jans', + 'inum=1800.1DA5BA,ou=scopes,o=jans', + 'inum=1800.926A52,ou=scopes,o=jans', + 'inum=1800.FF17D8,ou=scopes,o=jans', + 'inum=1800.A650F6,ou=scopes,o=jans', + 'inum=1800.580BA9,ou=scopes,o=jans', + 'inum=1800.DC882D,ou=scopes,o=jans', + 'inum=1800.F97827,ou=scopes,o=jans', + 'inum=1800.8FB9E4,ou=scopes,o=jans', + 'inum=1800.0EE56D,ou=scopes,o=jans', + 'inum=1800.FD8734,ou=scopes,o=jans', + 'inum=1800.73E5B5,ou=scopes,o=jans', + 'inum=1800.0DE56F,ou=scopes,o=jans', + 'inum=1800.F0C44A,ou=scopes,o=jans', + 'inum=1800.70AA30,ou=scopes,o=jans', + 'inum=1800.632780,ou=scopes,o=jans', + 'inum=1800.2646DC,ou=scopes,o=jans', + 'inum=1800.54387F,ou=scopes,o=jans', + 'inum=1800.16B85E,ou=scopes,o=jans', + 'inum=1800.565A62,ou=scopes,o=jans', + 'inum=1800.065E1A,ou=scopes,o=jans', + 'inum=1800.4E54A5,ou=scopes,o=jans', + 'inum=1800.50BBFE,ou=scopes,o=jans', + 'inum=1800.6A2BD2,ou=scopes,o=jans', + 'inum=1800.B9BE21,ou=scopes,o=jans', + 'inum=1800.A8FD82,ou=scopes,o=jans', + 'inum=1800.BDA282,ou=scopes,o=jans', + 'inum=1800.0B7536,ou=scopes,o=jans', + 'inum=1800.8100D9,ou=scopes,o=jans', + 'inum=1800.987C52,ou=scopes,o=jans', + 'inum=1800.DB17D4,ou=scopes,o=jans', + 'inum=1800.AA15F1,ou=scopes,o=jans', + 'inum=1800.5B28B1,ou=scopes,o=jans', + 'inum=1800.53B808,ou=scopes,o=jans', + 'inum=1800.573EBC,ou=scopes,o=jans', + 'inum=2762b464-0b67-4071-8072-91cb371895a6, + ou=scopes,o=jans', + 'inum=378ac51e-6746-4b2c-a9dc-ca3261f998e1, + ou=scopes,o=jans', + 'inum=478e15ec-1871-4314-a909-3d2c84bd09f9, + ou=scopes,o=jans'], + 'trustedClient': False, + 'persistClientAuthorizations': True, + 'includeClaimsInIdToken': False, + 'customAttributes': [{'name': 'displayName', + 'multiValued': False, + 'values': ['Jans Config Api Client'], + 'displayValue': 'Jans Config Api Client', + 'value': 'Jans Config Api Client'}], + 'customObjectClasses': ['top'], + 'rptAsJwt': False, + 'accessTokenAsJwt': False, + 'accessTokenSigningAlg': 'RS256', + 'disabled': False, + 'attributes': {'runIntrospectionScriptBeforeAccessTokenAsJwtCreationAndIncludeClaims': False, + 'keepClientAuthorizationAfterExpiration': False, + 'allowSpontaneousScopes': False, + 'backchannelLogoutSessionRequired': False, + 'parLifetime': 600, + 'requirePar': False, + 'jansDefaultPromptLogin': False}, + 'displayName': 'Jans Config Api Client', + 'authenticationMethod': 'client_secret_basic', + 'tokenBindingSupported': False, + 'baseDn': 'inum=1800.6c5faab4-dc28-4d46-94f4-df320f7c9780, + ou=clients,o=jans', + 'inum': '1800.6c5faab4-dc28-4d46-94f4-df320f7c9780' + } diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index d722f52ff25..eda48ead65c 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -6,7 +6,6 @@ from shutil import get_terminal_size import time from asyncio import Future, ensure_future -from pynput.keyboard import Key, Controller import prompt_toolkit from prompt_toolkit.application import Application @@ -22,6 +21,7 @@ VerticalAlign, DynamicContainer, FloatContainer, + Window ) from prompt_toolkit.layout.containers import VerticalAlign from prompt_toolkit.layout.dimension import D @@ -89,7 +89,7 @@ def __init__(self): self.app_started = False self.width, self.height = get_terminal_size() self.app = get_app() - self.show_dialog = False + self.show_dialog = False ## ## ## ## self.set_keybindings() self.containers = {} # -------------------------------------------------------------------------------- # @@ -98,7 +98,10 @@ def __init__(self): self.active_dialog_select = '' self.Auth_clients_tabs = {} - self.keyboard = Controller() + # ----------------------------------------------------------------------------- # + self.check_jans_cli_ini() + # ----------------------------------------------------------------------------- # + self.yes_button = Button(text="Yes", handler=accept_yes) self.no_button = Button(text="No", handler=accept_no) @@ -157,18 +160,7 @@ def __init__(self): # Since first module is oauth, set center frame to my oauth main container. self.oauth_set_center_frame() - - # ----------------------------------------------------------------------------- # - self.check_jans_cli_ini() - # ----------------------------------------------------------------------------- # - - - def press_tab(self): - self.keyboard.press(Key.tab) - self.keyboard.release(Key.tab) - def create_cli(self): - conn_ok = False test_client = config_cli.client_id if config_cli.test_client else None self.cli_object = config_cli.JCA_CLI( host=config_cli.host, @@ -180,14 +172,12 @@ def create_cli(self): status = self.cli_object.check_connection() - self.press_tab() - if status is not True: buttons = [Button("OK", handler=self.jans_creds_dialog)] self.show_message("Error getting Connection Config Api", status, buttons=buttons) else: if not test_client and not self.cli_object.access_token: - + response = self.cli_object.get_device_verification_code() result = response.json() @@ -196,7 +186,7 @@ def create_cli(self): ) body = HSplit([Label(msg)]) - dialog = JansGDialog(title="Waiting Response", body=body) + dialog = JansGDialog(parent=self,title="Waiting Response", body=body) async def coroutine(): app = get_app() @@ -207,17 +197,11 @@ async def coroutine(): app.layout.focus(focused_before) except: app.layout.focus(self.center_frame) - try: - self.cli_object.get_jwt_access_token(result) - except Exception as e: - err_dialog = JansGDialog(title="Error!", body=HSplit([Label(str(e))])) - await self.show_dialog_as_float(err_dialog) - self.create_cli() + + self.cli_object.get_jwt_access_token(result) ensure_future(coroutine()) - #if not conn_ok: - # self.create_cli() def check_jans_cli_ini(self): if not(config_cli.host and (config_cli.client_id and config_cli.client_secret or config_cli.access_token)): @@ -234,7 +218,7 @@ def dialog_back_but(self): ## BACK def prapare_dialogs(self): self.data_show_client_dialog = Label(text='Selected Line Data as Json') - self.dialog_width = int(self.width*0.9) # 120 ## to be dynamic + self.dialog_width = int(self.width*0.9) # 120 ## to be dynamic self.dialog_height = int(self.height*0.8) ## to be dynamic @@ -252,38 +236,85 @@ def set_keybindings(self): self.bindings.add("tab")(self.focus_next) self.bindings.add("s-tab")(self.focus_previous) self.bindings.add("c-c")(do_exit) + # ----------------------------------------------------------------- # + def handel_long_string (self,text,values,cb): + lines = [] + if len(text) > 20 : + title_list=text.split(' ') + dum = '' + for i in range(len(title_list)): + if len(dum) < 20 : + if len(title_list[i] + dum) < 30 : + dum+=title_list[i] +' ' + else : + lines.append(dum.strip()) + dum = title_list[i] + ' ' + else : + lines.append(dum.strip()) + dum = title_list[i] + ' ' + lines.append(dum) + num_lines = len(lines) + width = len(max(lines, key=len)) + else: + width = len(text) + lines.append(text) + num_lines = len(lines) + - def getTitledText(self, title, name, value='', height=1, jans_help='', width=None): + new_title,title_lines = '\n'.join(lines) , num_lines + + + if title_lines <= len(values) : ### if num of values (value lines) < = title_lines + lines_under_value = 0 + else : + lines_under_value = abs(title_lines-len(values)) + + if lines_under_value !=0 : + cd = HSplit([ + cb, + Label(text=('\n')*(lines_under_value-1)) + ]) + else : + cd = cb + + if title_lines <= len(values) : ### if num of values (value lines) < = title_lines + lines_under_title = abs(len(values) - title_lines) + else : + lines_under_title = 0 + + # first one >> solved >> title + if lines_under_title >=1 : + for i in range(lines_under_title): + new_title +='\n' + else : + pass + + return new_title , cd , width + + def getTitledText(self, title, name, value='', height=1, jans_help='', width=None,style=''): multiline = height > 1 - ta = TextArea(text=value, multiline=multiline, style='class:textarea') + ta = TextArea(text=value, multiline=multiline,style="class:titledtext") ta.window.jans_name = name ta.window.jans_help = jans_help - if width: - ta.window.width = width - li = title - for i in range(height-1): - li +='\n' + li,cd,width = self.handel_long_string(title,[1]*height,ta) - return VSplit([Label(text=li + ':', width=len(title)+1), ta], height=height, padding=1) - - def getTitledCheckBox(self, title, name, values): - cb = CheckboxList(values=[(o,o) for o in values],) + return VSplit([Label(text=li, width=width,style=style), cd], padding=1) + + def getTitledCheckBox(self, title, name, values,style=''): + cb = CheckboxList(values=[(o,o) for o in values]) cb.window.jans_name = name - li = title - for i in range(len(values)-1): - li +='\n' - return VSplit([Label(text=li, width=len(title)+1), cb] ) + li,cd,width = self.handel_long_string(title,values,cb) + + return VSplit([Label(text=li, width=width,style=style,wrap_lines=False), cd]) - def getTitledRadioButton(self, title, name, values): + def getTitledRadioButton(self, title, name, values,style=''): rl = RadioList(values=[(option, option) for option in values]) rl.window.jans_name = name - li = title - for i in range(len(values)-1): - li +='\n' + li,rl2,width = self.handel_long_string(title,values,rl) + + return VSplit([Label(text=li, width=width,style=style), rl2],) - return VSplit([Label(text=li, width=len(title)+1), rl], - height=len(values) - ) + # ----------------------------------------------------------------- # def getButton(self, text, name, jans_help, handler=None): b = Button(text=text, width=len(text)+2) @@ -350,8 +381,6 @@ async def coroutine(): ensure_future(coroutine()) - - def data_display_dialog(self, **params): body = HSplit([ @@ -365,7 +394,7 @@ def data_display_dialog(self, **params): ) ]) - dialog = JansGDialog(title=params['selected'][0], body=body) + dialog = JansGDialog(parent=self,title=params['selected'][0], body=body) self.show_jans_dialog(dialog) @@ -381,23 +410,26 @@ def save_creds(self, dialog): config_cli.client_id = config_cli.config['DEFAULT']['jca_client_id'] config_cli.client_secret = config_cli.config['DEFAULT']['jca_client_secret'] - def jans_creds_dialog(self, *params): + # body=HSplit([ + # self.getTitledText("Hostname", name='jans_host', value=config_cli.host or '', jans_help="FQN name of Jannsen Config Api Server",), + # self.getTitledText("Client ID", name='jca_client_id', value=config_cli.client_id or '', jans_help="Jannsen Config Api Client ID"), + # self.getTitledText("Client Secret", name='jca_client_secret', value=config_cli.client_secret or '', jans_help="Jannsen Config Api Client Secret") + # ]) body=HSplit([ - self.getTitledText("Hostname", name='jans_host', value=config_cli.host or '', jans_help="FQN name of Jannsen Config Api Server"), - self.getTitledText("Client ID", name='jca_client_id', value=config_cli.client_id or '', jans_help="Jannsen Config Api Client ID"), - self.getTitledText("Client Secret", name='jca_client_secret', value=config_cli.client_secret or '', jans_help="Jannsen Config Api Client Secret") + self.getTitledText("Hostname", name='jans_host', value=config_cli.host or 'c1.gluu.org', jans_help="FQN name of Jannsen Config Api Server",), + self.getTitledText("Client ID", name='jca_client_id', value=config_cli.client_id or '2000.2efa2c06-f711-42d6-a02c-4dcb5191669c', jans_help="Jannsen Config Api Client ID"), + self.getTitledText("Client Secret", name='jca_client_secret', value=config_cli.client_secret or 'd6yHY6lUJMgl', jans_help="Jannsen Config Api Client Secret") ]) - buttons = [Button("Save", handler=self.save_creds)] - dialog = JansGDialog(title="Janssen Config Api Client Credidentials", body=body, buttons=buttons) + dialog = JansGDialog(parent=self,title="Janssen Config Api Client Credidentials", body=body, buttons=buttons) async def coroutine(): app = get_app() focused_before = app.layout.current_window self.layout.focus(dialog) - result = await self.show_dialog_as_float(dialog) + result = await self.show_dialog_as_float(dialog) try: app.layout.focus(focused_before) except: @@ -407,22 +439,72 @@ async def coroutine(): ensure_future(coroutine()) - - def edit_client_dialog(self, **params): - dialog = EditClientDialog(self,**params) + + navBar = JansSideNavBar(myparent=self, + entries=list( + self.oauth_tabs['clients'].keys()), + selection_changed=( + self.client_dialog_nav_selection_changed), + select=0, + entries_color='#2600ff') + selected_line_data = params['data'] ## if we want to makeuse of it + body = VSplit([ + HSplit( + [ + navBar + ], + width=30 + ), + Window(width=1, char="|",), + HSplit( + [ + DynamicContainer( + lambda: self.oauth_tabs['clients'][self.oauth_dialog_nav]) + ],width=D()), + ], height=32) + dialog = JansGDialog(parent=self,title="Edit user Data (Clients)", body=body) self.show_jans_dialog(dialog) def edit_scope_dialog(self, **params): - dialog = EditScopeDialog(self,**params) + + navBar = JansSideNavBar(myparent=self, + entries=list( + self.oauth_tabs['clients'].keys()), + selection_changed=( + self.client_dialog_nav_selection_changed), + select=0, + entries_color='#2600ff') + + selected_line_data = params['data'] ## if we want to makeuse of it + + body = VSplit([ + HSplit( + [ + navBar + ], + width=30 + ), + Window(width=1, char="|",), + HSplit( + [ + DynamicContainer( + lambda: self.oauth_tabs['clients'][self.oauth_dialog_nav]) + ]), + ], height=32) + + dialog = JansGDialog(self,title="Edit Scope Data (Clients)", body=body) self.show_jans_dialog(dialog) def show_message(self, title, message, buttons=[]): body = HSplit([Label(message)]) - dialog = JansGDialog(title=title, body=body, buttons=buttons) + dialog = JansGDialog(parent=self,title=title, body=body, buttons=buttons) self.show_jans_dialog(dialog) - + def show_again(self): ## nasted dialog Button + self.show_message("Again", "Nasted Dialogs",) + + application = JansCliApp() def run(): @@ -431,4 +513,4 @@ def run(): if __name__ == "__main__": - run() + run() \ No newline at end of file diff --git a/jans-cli-tui/models/oauth.py b/jans-cli-tui/models/oauth.py old mode 100644 new mode 100755 index 8ca4a5664ba..4c44066de34 --- a/jans-cli-tui/models/oauth.py +++ b/jans-cli-tui/models/oauth.py @@ -57,57 +57,205 @@ def oauth_prepare_tabs(self): self.oauth_tabs = {} self.oauth_tabs['clients'] = OrderedDict() - self.oauth_tabs['clients']['Person Authentication'] = HSplit([ + self.oauth_tabs['clients']['Basic'] = HSplit([ VSplit([ - # Label(text=str('Client_ID')), - #TextArea(text='', name='Client_ID'), - self.getTitledText("Client_ID", name='Client_ID'), + self.getTitledText("Client_ID", name='Client_ID',style='green'), Window(width=1, char=" ",), - self.getTitledCheckBox("Active:", name='active', values=['active']), + self.getTitledCheckBox("Active:", name='active', values=['active'],style='green'), ]) , - self.getTitledText("Client Name:", name='displayName'), - self.getTitledText("Client Secret:", name='clientSecret'), - self.getTitledText("Description:", name='Description'), - self.getTitledRadioButton("Id_token Subject Type:", name='Id_token', values=['Pairwise','Public'],), - self.getTitledCheckBox("Grant:", name='Grant', values=['Authorizatuin Code', 'Refresh Token', 'UMA Ticket','Client Credential','Password','Implicit']), - self.getTitledCheckBox("Response Types:", name='responseTypes', values=['code', 'token', 'id_token']), - self.getTitledCheckBox("Supress Authorization:", name='Supress', values=['True']), - self.getTitledRadioButton("Application Type:", name='applicationType', values=['web', 'native']), - self.getTitledText("Redirect Uris:", name='redirectUris', height=3), - self.getTitledText("Redirect Regex:", name='redirectregex'), - self.getTitledText("Scopes:", name='scopes', height=3), + self.getTitledText("Client Name", name='displayName',style='green'), + self.getTitledText("Client Secret", name='clientSecret',style='green'), + self.getTitledText("Description", name='Description',style='green'), + self.getTitledRadioButton("Id_token Subject Type:", name='Id_token', values=['Pairwise','Public'],style='green'), + self.getTitledCheckBox("Grant:", name='Grant', values=['Authorizatuin Code', 'Refresh Token', 'UMA Ticket','Client Credential','Password','Implicit'],style='green'), + self.getTitledCheckBox("Response Types:", name='responseTypes', values=['code', 'token', 'id_token'],style='green'), + self.getTitledCheckBox("Supress Authorization:", name='Supress', values=['True'],style='green'), + self.getTitledRadioButton("Application Type:", name='applicationType', values=['web', 'native'],style='green'), + self.getTitledText("Redirect Uris", name='redirectUris', height=3,style='green'), + self.getTitledText("Redirect Regex", name='redirectregex',style='green'), + self.getTitledText("Scopes", name='scopes', height=3,style='green'), + # HSplit([Box(body=VSplit([self.yes_button, self.no_button], align="CENTER", padding=3), style="class:button-bar", height=1)], + # height=D(), + # align = VerticalAlign.BOTTOM, + # ) + ],width=D(), + ) + + self.oauth_tabs['clients']['Tokens'] = HSplit([ + self.getTitledRadioButton("Access Token Type", name='access_token_type', values=['JWT','Reference'],style='green'), + self.getTitledCheckBox("Incliude Claims in id_token", name='id_token_claims', values=['True'],style='green'), + self.getTitledCheckBox("Run introspection script before JWT access token creation", name='Supress_JWT', values=['True'],style='green'), + self.getTitledText("Token binding confirmation method for id_token", name='id_token_binding_confirmation',style='green'), + self.getTitledText("Access token additional audiences", name='access_token_audiences',style='green'), + VSplit([ + Button("+", handler=self.show_again,left_symbol='[',right_symbol=']',width=3) + ]), + self.getTitledText("Access token lifetime", name='access_token_lifetime',style='green'), + self.getTitledText("Refresh token lifetime", name='refresh_token_lifetime',style='green'), + self.getTitledText("Defult max authn age", name='max_authn_age',style='green'), + + ],width=D()) + + self.oauth_tabs['clients']['Logout'] = HSplit([ + + self.getTitledText("Front channel logout URI", name='f_channel_logout_URI',style='green'), + self.getTitledText("Post logout redirect URI", name='p_channel_logout_redirect_URI',style='green'), + self.getTitledText("Back channel logout URI", name='b_channel_logout_URI',style='green'), + self.getTitledCheckBox("Back channel logout session required", name='b_channel_session_required', values=['True'],style='green'), + self.getTitledCheckBox("Front channel logout session required", name='f_channel_session_required', values=['True'],style='green'), + + ],width=D() + ) + + self.oauth_tabs['clients']['Software Info'] = HSplit([ + self.getTitledText("Client URI", name='client_URI',style='green'), + self.getTitledText("Policy URI", name='policy_URI',style='green'), + self.getTitledText("Logo URI", name='logo_URI',style='green'), + self.getTitledText("Term of service URI", name='term_of_service_URI',style='green'), + self.getTitledText("Contacts", name='contacts',style='green'), + VSplit([ + Button("+", handler=self.show_again,left_symbol='[',right_symbol=']',width=3,) + ]), + self.getTitledText("Authorized JS origins", name='authorized_JS_origins',style='green'), + VSplit([ + Button("+", handler=self.show_again,left_symbol='[',right_symbol=']',width=3) + ]), + self.getTitledText("Software id", name='software_id',style='green'), + self.getTitledText("Software version", name='software_version',style='green'), + self.getTitledText("Software statement", name='software_statement',style='green'), + + ],width=D()) + self.oauth_tabs['clients']['CIBA/PAR/UMA'] = HSplit([ + Label(text="CIBA",style='bold'), + self.getTitledRadioButton("Token delivery method", name='applicationType', values=['poll','push', 'ping'],style='green'), + self.getTitledText("Client notification endpoint", name='displayName',style='green'), + self.getTitledCheckBox("Require user code param", name='Supress', values=['True'],style='green'), + + Label(text="PAR",style='bold'), + self.getTitledText("Request lifetime", name='displayName',style='green'), + self.getTitledCheckBox("Request PAR", name='Supress', values=['True'],style='green'), - HSplit([Box(body=VSplit([self.yes_button, self.no_button], align="CENTER", padding=3), style="class:button-bar", height=1)], - height=D(), - align = VerticalAlign.BOTTOM, + Label("UMA",style='bold'), + self.getTitledRadioButton("PRT token type", name='applicationType', values=['JWT', 'Reference'],style='green'), + self.getTitledText("Claims redirect URI", name='displayName',style='green'), + + Label(text="Dropdown 1",style='blue'), + Label(text="Dropdown 2",style='blue'), + Label(text="tabel",style='blue'), ## TODO with Jans VerticalNav + + # JansVerticalNav( + # myparent=self, + # headers=['Client ID', 'Client Name', 'Grant Types', 'Subject Type'], + # preferred_size= [0,0,30,0], + # data=[['1','2','3','4'],['1','2','3','4'],['1','2','3','4'],['1','2','3','4'],], + # on_enter=self.edit_client_dialog, + # on_display=self.data_display_dialog, + # # selection_changed=self.data_selection_changed, + # selectes=0, + # headerColor='green', + # entriesColor='white', + # all_data=[['1','2','3','4'],['1','2','3','4'],['1','2','3','4'],['1','2','3','4'],] + # ) + ] ) - ], + + self.oauth_tabs['clients']['Encryption/Signing'] = HSplit([ + self.getTitledText("Client JWKS URI", name='displayName',style='green'), + self.getTitledText("Client JWKS", name='displayName',style='green'), + VSplit([ + Label(text="id_token"), + Label(text="a, b, c",style='red'), + ]), + VSplit([ + Label(text="Access token"), + Label(text="a",style='red'), + ]), + VSplit([ + Label(text="Userinfo"), + Label(text="a, b, c",style='red'), + ]), + VSplit([ + Label(text="JARM"), + Label(text="a, b, c",style='red'), + ]), + VSplit([ + Label(text="Request Object"), + Label(text="a, b, c",style='red'), + ]), + + ] + ) + + self.oauth_tabs['clients']['Advanced Client Properties'] = HSplit([ + + self.getTitledCheckBox("Default Prompt=login", name='Supress', values=['True'],style='green'), + VSplit([ + self.getTitledCheckBox("Persist Authorizations", name='Supress', values=['True'],style='green'), + self.getTitledCheckBox("Keep expired?", name='Supress', values=['True'],style='green'), + ]) , + self.getTitledCheckBox("Allow spontaneos scopes", name='Supress', values=['True'],style='green'), + + self.getTitledText("spontaneos scopes validation regex", name='displayName',style='green'), + VSplit([ + Label(text="Spontaneous Scopes",style='green'), + Button("view current", handler=self.show_again,left_symbol='',right_symbol='',) + + ]) , + self.getTitledText("Initial Login URI", name='displayName',style='green'), + + VSplit([ + self.getTitledText("Request URIs", name='clientSecret',style='green'), + Button("+", handler=self.show_again,left_symbol='[',right_symbol=']',width=3,) + ]) , + + Label(text="Dropdown 3",style='blue'), + + VSplit([ + self.getTitledText("Allowed ACRs", name='clientSecret',style='green'), + Button("+", handler=self.show_again,left_symbol='[',right_symbol=']',width=3,) + ]) , + self.getTitledText("TLS Subject DN", name='clientSecret',style='green'), + + VSplit([ + self.getTitledCheckBox("Client Experiation Date", name='id_token_claims', values=['True'],style='green'), + Label(text="Pick Date",style='blue'), + ]) , + + + + ],width=D() + ) + + self.oauth_tabs['clients']['Client Scripts'] = HSplit([ + Label(text="Dropdown 4",style='blue'), + Label(text="Dropdown 5",style='blue'), + Label(text="Dropdown 6",style='blue'), + Label(text="Dropdown 7",style='blue'), + Label(text="Dropdown 8",style='blue'), + ] + ) + + + self.oauth_tabs['clients']['Save'] = HSplit([ + Button("Save", handler=self.show_again,left_symbol='(',right_symbol=')',) + + ],width=D() ) - self.oauth_tabs['clients']['Consent Gathering'] = Label(text=str('Consent Gathering')) - self.oauth_tabs['clients']['Post Authentication'] = Label(text=str('Post Authentication')) - self.oauth_tabs['clients']['id_token'] = Label(text=str('id_token')) - self.oauth_tabs['clients']['Password Grant'] = Label(text=str('Password Grant')) - self.oauth_tabs['clients']['CIBA End User Notification'] = Label(text=str('CIBA End User Notification')) - self.oauth_tabs['clients']['OpenId Configuration'] = Label(text=str('OpenId Configuration')) - self.oauth_tabs['clients']['Dynamic Scope'] = Label(text=str('Dynamic Scope')) - self.oauth_tabs['clients']['Spontaneous Scope'] = Label(text=str('Spontaneous Scope')) - self.oauth_tabs['clients']['End Session'] = Label(text=str('End Session')) - self.oauth_tabs['clients']['Client Registation'] = Label(text=str('Client Registation')) - self.oauth_tabs['clients']['Introseption'] = Label(text=str('Introseption')) - self.oauth_tabs['clients']['Update Token'] = Label(text=str('Update Token')) + + self.oauth_dialog_nav = list(self.oauth_tabs['clients'].keys())[0] def client_dialog_nav_selection_changed(self, selection): self.oauth_dialog_nav = selection - def edit_client(self, selected,event,size): ## enter - self.edit_client_dialog() - # self.active_dialog_select = 'enter' - # self.show_dialog = True + # def edit_client(self, selected,event,size): ## enter + # self.edit_client_dialog() + # # self.active_dialog_select = 'enter' + # # self.show_dialog = True - # event.app.layout.focus(self.my_dialogs()) + # # event.app.layout.focus(self.my_dialogs()) def oauth_prepare_containers(self): @@ -207,7 +355,7 @@ def oauth_update_clients(self): headers=['Client ID', 'Client Name', 'Grant Types', 'Subject Type'], preferred_size= [0,0,30,0], data=data, - on_enter=self.edit_client, + on_enter=self.edit_client_dialog, on_display=self.data_display_dialog, # selection_changed=self.data_selection_changed, selectes=0, @@ -282,4 +430,4 @@ def display_scope(self): pass def edit_scope(self, selected,event,size): ## enter - self.edit_scope_dialog() + self.edit_scope_dialog() \ No newline at end of file diff --git a/jans-cli-tui/static.py b/jans-cli-tui/static.py old mode 100644 new mode 100755 diff --git a/jans-cli-tui/test_logstring.py b/jans-cli-tui/test_logstring.py new file mode 100644 index 00000000000..193b7adcdc4 --- /dev/null +++ b/jans-cli-tui/test_logstring.py @@ -0,0 +1,73 @@ + + def handel_long_string (self,text,values,cb): + lines = [] + if len(text) > 20 : + title_list=text.split(' ') + dum = '' + for i in range(len(title_list)): + if len(dum) < 20 : + if len(title_list[i] + dum) < 30 : + dum+=title_list[i] +' ' + else : + lines.append(dum.strip()) + dum = title_list[i] + ' ' + else : + lines.append(dum.strip()) + dum = title_list[i] + ' ' + lines.append(dum) + num_lines = len(lines) + width = len(max(lines, key=len)) + else: + width = len(text) + lines.append(text) + num_lines = len(lines) + + + new_title,title_lines = '\n'.join(lines) , num_lines + + + if title_lines <= len(values) : ### if num of values (value lines) < = title_lines + lines_under_value = 0 + else : + lines_under_value = abs(title_lines-len(values)) + + if lines_under_value !=0 : + cd = HSplit([ + cb, + Label(text=('\n')*(lines_under_value-1)) + ]) + else : + cd = cb + + if title_lines <= len(values) : ### if num of values (value lines) < = title_lines + lines_under_title = abs(len(values) - title_lines) + else : + lines_under_title = 0 + + # first one >> solved >> title + if lines_under_title >=1 : + for i in range(lines_under_title): + new_title +='\n' + else : + pass + + return new_title , cd , width + + + + def getTitledCheckBox(self, title, name, values,style=''): + ## two problem here + ## first one is the space under title (if many values) + ## Sec one is the space under values (if multiline title) + + + cb = CheckboxList(values=[(o,o) for o in values],) + cb.window.jans_name = name + + li,cd,width = self.handel_long_string(title,values,cb) + + + return VSplit([Label(text=li, width=width,style=style,wrap_lines=False), cd]) + + + diff --git a/jans-cli-tui/wrapper_test.py b/jans-cli-tui/wrapper_test.py old mode 100644 new mode 100755 diff --git a/jans-cli-tui/wui_components/edit_client_dialog.py b/jans-cli-tui/wui_components/edit_client_dialog.py old mode 100644 new mode 100755 index ee47573806b..9df315942ab --- a/jans-cli-tui/wui_components/edit_client_dialog.py +++ b/jans-cli-tui/wui_components/edit_client_dialog.py @@ -91,4 +91,3 @@ def __pt_container__(self): - diff --git a/jans-cli-tui/wui_components/edit_scope_dialog.py b/jans-cli-tui/wui_components/edit_scope_dialog.py old mode 100644 new mode 100755 index b051bc58a63..ad059cdf710 --- a/jans-cli-tui/wui_components/edit_scope_dialog.py +++ b/jans-cli-tui/wui_components/edit_scope_dialog.py @@ -91,4 +91,3 @@ def __pt_container__(self): - diff --git a/jans-cli-tui/wui_components/jans_cli_dialog.py b/jans-cli-tui/wui_components/jans_cli_dialog.py old mode 100644 new mode 100755 index 29dbff9d62b..5cca3a56244 --- a/jans-cli-tui/wui_components/jans_cli_dialog.py +++ b/jans-cli-tui/wui_components/jans_cli_dialog.py @@ -8,24 +8,35 @@ from static import DialogResult from functools import partial class JansGDialog: - def __init__(self, title, body, buttons=[]): + def __init__(self, parent, title, body, buttons=[]): + + self.result = None self.future = Future() self.body = body - if not buttons: - buttons = [Button(text="OK")] - def do_handler(button_text, handler): if handler: handler(self) self.future.set_result(button_text) + def exit_me(button): + if not getattr(button, 'keep_me', False): + parent.root_layout.floats.pop() + if parent.root_layout.floats: + parent.layout.focus(parent.root_layout.floats[-1].content) + else: + parent.layout.focus(parent.center_frame) + + if not buttons: + buttons = [Button(text="OK")] + for button in buttons: - button.handler = partial(do_handler, button.text, button.handler) + button.handler = partial(do_handler, button, button.handler) + self.dialog = Dialog( title=title, - body=body, + body=self.body, buttons=buttons, width=D(preferred=80), modal=True, diff --git a/jans-cli-tui/wui_components/jans_dialog.py b/jans-cli-tui/wui_components/jans_dialog.py old mode 100644 new mode 100755 index 91c7d5736ec..5cf647db1c3 --- a/jans-cli-tui/wui_components/jans_dialog.py +++ b/jans-cli-tui/wui_components/jans_dialog.py @@ -43,4 +43,3 @@ def create_window(self): #--------------------------------------------------------------------------------------# def __pt_container__(self): return self.dialog - diff --git a/jans-cli-tui/wui_components/jans_dialog_with_nav.py b/jans-cli-tui/wui_components/jans_dialog_with_nav.py old mode 100644 new mode 100755 index d068e9e931e..ed2ab622c8f --- a/jans-cli-tui/wui_components/jans_dialog_with_nav.py +++ b/jans-cli-tui/wui_components/jans_dialog_with_nav.py @@ -54,4 +54,3 @@ def create_window(self): #--------------------------------------------------------------------------------------# def __pt_container__(self): return self.dialog - diff --git a/jans-cli-tui/wui_components/jans_nav_bar.py b/jans-cli-tui/wui_components/jans_nav_bar.py old mode 100644 new mode 100755 index e91cc2be899..dee5225b94a --- a/jans-cli-tui/wui_components/jans_nav_bar.py +++ b/jans-cli-tui/wui_components/jans_nav_bar.py @@ -60,4 +60,4 @@ def _go_up(event) -> None: self.cur_navbar_selection = (self.cur_navbar_selection + 1) % len(self.navbar_entries) self.update_status_bar() - return kb + return kb \ No newline at end of file diff --git a/jans-cli-tui/wui_components/jans_side_nav_bar.py b/jans-cli-tui/wui_components/jans_side_nav_bar.py old mode 100644 new mode 100755 index 78da832d5b9..45cf9875094 --- a/jans-cli-tui/wui_components/jans_side_nav_bar.py +++ b/jans-cli-tui/wui_components/jans_side_nav_bar.py @@ -84,4 +84,4 @@ def _(event): return kb def __pt_container__(self): - return self.side_Nav + return self.side_Nav \ No newline at end of file diff --git a/jans-cli-tui/wui_components/jans_vetrical_nav.py b/jans-cli-tui/wui_components/jans_vetrical_nav.py old mode 100644 new mode 100755 index 845f384189d..cd45ffff15a --- a/jans-cli-tui/wui_components/jans_vetrical_nav.py +++ b/jans-cli-tui/wui_components/jans_vetrical_nav.py @@ -106,16 +106,16 @@ def handel_Header_spaces(self): for i in self.mod_data[dataline]: line.append(len(i)) datalen.append(line) - dict = {} + sp_dict = {} for num in range(len(datalen[0])): - dict[num] = [] + sp_dict [num] = [] for k in range(len(datalen)): for i in range(len(datalen[k])): - dict[i].append(datalen[k][i]) + sp_dict [i].append(datalen[k][i]) - for i in dict: - self.spaces.append(max(dict[i])) + for i in sp_dict : + self.spaces.append(max(sp_dict [i])) for i in range(len(self.spaces)): ## handel header collesion (when the headers length is greater that the tallest data index length) @@ -174,8 +174,7 @@ def _go_up(event) -> None: def _(event): passed = [i.strip() for i in self.data[self.selectes]] size = self.myparent.output.get_size() - self.on_enter(passed,event,size) - + self.on_enter(passed=passed,event=event,size=size,data=self.all_data[self.selectes]) @kb.add("d") def _(event): @@ -194,4 +193,4 @@ def _(event): # -------------------------------------------------------------------------------- # def __pt_container__(self): - return self.container + return self.container \ No newline at end of file From 7034bfe4c31a9189911ae18f132fdc41b0d47d67 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Tue, 16 Aug 2022 06:15:50 -0700 Subject: [PATCH 012/364] hopa(16-8-2022) --- jans-cli-tui/jans-cli-tui.py | 100 ++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 49 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index eda48ead65c..f29a33262a1 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -160,6 +160,56 @@ def __init__(self): # Since first module is oauth, set center frame to my oauth main container. self.oauth_set_center_frame() + # ------------------------------------------------------ # + + def check_jans_cli_ini(self): + if not(config_cli.host and (config_cli.client_id and config_cli.client_secret or config_cli.access_token)): + self.jans_creds_dialog() + else : + self.create_cli() + + def save_creds(self, dialog): + + for child in dialog.body.children: + prop_name = child.children[1].jans_name + prop_val = child.children[1].content.buffer.text + config_cli.config['DEFAULT'][prop_name] = prop_val + config_cli.write_config() + + config_cli.host = config_cli.config['DEFAULT']['jans_host'] + config_cli.client_id = config_cli.config['DEFAULT']['jca_client_id'] + config_cli.client_secret = config_cli.config['DEFAULT']['jca_client_secret'] + self.create_cli() + + def jans_creds_dialog(self, *params): + + # body=HSplit([ + # self.getTitledText("Hostname", name='jans_host', value=config_cli.host or '', jans_help="FQN name of Jannsen Config Api Server",), + # self.getTitledText("Client ID", name='jca_client_id', value=config_cli.client_id or '', jans_help="Jannsen Config Api Client ID"), + # self.getTitledText("Client Secret", name='jca_client_secret', value=config_cli.client_secret or '', jans_help="Jannsen Config Api Client Secret") + # ]) + body=HSplit([ + self.getTitledText("Hostname", name='jans_host', value=config_cli.host or 'c1.gluu.org', jans_help="FQN name of Jannsen Config Api Server",), + self.getTitledText("Client ID", name='jca_client_id', value=config_cli.client_id or '2000.2efa2c06-f711-42d6-a02c-4dcb5191669c', jans_help="Jannsen Config Api Client ID"), + self.getTitledText("Client Secret", name='jca_client_secret', value=config_cli.client_secret or 'd6yHY6lUJMgl', jans_help="Jannsen Config Api Client Secret") + ]) + buttons = [Button("Save", handler=self.save_creds)] + dialog = JansGDialog(parent=self,title="Janssen Config Api Client Credidentials", body=body, buttons=buttons) + + async def coroutine(): + app = get_app() + focused_before = app.layout.current_window + self.layout.focus(dialog) + result = await self.show_dialog_as_float(dialog) + try: + app.layout.focus(focused_before) + except: + app.layout.focus(self.center_frame) + + + + ensure_future(coroutine()) + def create_cli(self): test_client = config_cli.client_id if config_cli.test_client else None self.cli_object = config_cli.JCA_CLI( @@ -202,26 +252,18 @@ async def coroutine(): ensure_future(coroutine()) - - def check_jans_cli_ini(self): - if not(config_cli.host and (config_cli.client_id and config_cli.client_secret or config_cli.access_token)): - self.jans_creds_dialog() - else : - self.create_cli() - + # ------------------------------------------------------ # def dialog_back_but(self): ## BACK self.active_dialog_select = '' self.show_dialog = False self.layout.focus(self.center_frame) - def prapare_dialogs(self): self.data_show_client_dialog = Label(text='Selected Line Data as Json') self.dialog_width = int(self.width*0.9) # 120 ## to be dynamic self.dialog_height = int(self.height*0.8) ## to be dynamic - def focus_next(self, ev): focus_next(ev) self.update_status_bar() @@ -398,46 +440,6 @@ def data_display_dialog(self, **params): self.show_jans_dialog(dialog) - def save_creds(self, dialog): - - for child in dialog.body.children: - prop_name = child.children[1].jans_name - prop_val = child.children[1].content.buffer.text - config_cli.config['DEFAULT'][prop_name] = prop_val - config_cli.write_config() - - config_cli.host = config_cli.config['DEFAULT']['jans_host'] - config_cli.client_id = config_cli.config['DEFAULT']['jca_client_id'] - config_cli.client_secret = config_cli.config['DEFAULT']['jca_client_secret'] - - def jans_creds_dialog(self, *params): - - # body=HSplit([ - # self.getTitledText("Hostname", name='jans_host', value=config_cli.host or '', jans_help="FQN name of Jannsen Config Api Server",), - # self.getTitledText("Client ID", name='jca_client_id', value=config_cli.client_id or '', jans_help="Jannsen Config Api Client ID"), - # self.getTitledText("Client Secret", name='jca_client_secret', value=config_cli.client_secret or '', jans_help="Jannsen Config Api Client Secret") - # ]) - body=HSplit([ - self.getTitledText("Hostname", name='jans_host', value=config_cli.host or 'c1.gluu.org', jans_help="FQN name of Jannsen Config Api Server",), - self.getTitledText("Client ID", name='jca_client_id', value=config_cli.client_id or '2000.2efa2c06-f711-42d6-a02c-4dcb5191669c', jans_help="Jannsen Config Api Client ID"), - self.getTitledText("Client Secret", name='jca_client_secret', value=config_cli.client_secret or 'd6yHY6lUJMgl', jans_help="Jannsen Config Api Client Secret") - ]) - buttons = [Button("Save", handler=self.save_creds)] - dialog = JansGDialog(parent=self,title="Janssen Config Api Client Credidentials", body=body, buttons=buttons) - - async def coroutine(): - app = get_app() - focused_before = app.layout.current_window - self.layout.focus(dialog) - result = await self.show_dialog_as_float(dialog) - try: - app.layout.focus(focused_before) - except: - app.layout.focus(self.center_frame) - - self.create_cli() - - ensure_future(coroutine()) def edit_client_dialog(self, **params): From f064fb1d099fc9eb7c68447fd427f49da4c70e56 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Wed, 17 Aug 2022 11:13:11 +0300 Subject: [PATCH 013/364] Revert "hopa(16-8-2022)" This reverts commit 7034bfe4c31a9189911ae18f132fdc41b0d47d67. --- jans-cli-tui/jans-cli-tui.py | 100 +++++++++++++++++------------------ 1 file changed, 49 insertions(+), 51 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index f29a33262a1..eda48ead65c 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -160,56 +160,6 @@ def __init__(self): # Since first module is oauth, set center frame to my oauth main container. self.oauth_set_center_frame() - # ------------------------------------------------------ # - - def check_jans_cli_ini(self): - if not(config_cli.host and (config_cli.client_id and config_cli.client_secret or config_cli.access_token)): - self.jans_creds_dialog() - else : - self.create_cli() - - def save_creds(self, dialog): - - for child in dialog.body.children: - prop_name = child.children[1].jans_name - prop_val = child.children[1].content.buffer.text - config_cli.config['DEFAULT'][prop_name] = prop_val - config_cli.write_config() - - config_cli.host = config_cli.config['DEFAULT']['jans_host'] - config_cli.client_id = config_cli.config['DEFAULT']['jca_client_id'] - config_cli.client_secret = config_cli.config['DEFAULT']['jca_client_secret'] - self.create_cli() - - def jans_creds_dialog(self, *params): - - # body=HSplit([ - # self.getTitledText("Hostname", name='jans_host', value=config_cli.host or '', jans_help="FQN name of Jannsen Config Api Server",), - # self.getTitledText("Client ID", name='jca_client_id', value=config_cli.client_id or '', jans_help="Jannsen Config Api Client ID"), - # self.getTitledText("Client Secret", name='jca_client_secret', value=config_cli.client_secret or '', jans_help="Jannsen Config Api Client Secret") - # ]) - body=HSplit([ - self.getTitledText("Hostname", name='jans_host', value=config_cli.host or 'c1.gluu.org', jans_help="FQN name of Jannsen Config Api Server",), - self.getTitledText("Client ID", name='jca_client_id', value=config_cli.client_id or '2000.2efa2c06-f711-42d6-a02c-4dcb5191669c', jans_help="Jannsen Config Api Client ID"), - self.getTitledText("Client Secret", name='jca_client_secret', value=config_cli.client_secret or 'd6yHY6lUJMgl', jans_help="Jannsen Config Api Client Secret") - ]) - buttons = [Button("Save", handler=self.save_creds)] - dialog = JansGDialog(parent=self,title="Janssen Config Api Client Credidentials", body=body, buttons=buttons) - - async def coroutine(): - app = get_app() - focused_before = app.layout.current_window - self.layout.focus(dialog) - result = await self.show_dialog_as_float(dialog) - try: - app.layout.focus(focused_before) - except: - app.layout.focus(self.center_frame) - - - - ensure_future(coroutine()) - def create_cli(self): test_client = config_cli.client_id if config_cli.test_client else None self.cli_object = config_cli.JCA_CLI( @@ -252,18 +202,26 @@ async def coroutine(): ensure_future(coroutine()) - # ------------------------------------------------------ # + + def check_jans_cli_ini(self): + if not(config_cli.host and (config_cli.client_id and config_cli.client_secret or config_cli.access_token)): + self.jans_creds_dialog() + else : + self.create_cli() + def dialog_back_but(self): ## BACK self.active_dialog_select = '' self.show_dialog = False self.layout.focus(self.center_frame) + def prapare_dialogs(self): self.data_show_client_dialog = Label(text='Selected Line Data as Json') self.dialog_width = int(self.width*0.9) # 120 ## to be dynamic self.dialog_height = int(self.height*0.8) ## to be dynamic + def focus_next(self, ev): focus_next(ev) self.update_status_bar() @@ -440,6 +398,46 @@ def data_display_dialog(self, **params): self.show_jans_dialog(dialog) + def save_creds(self, dialog): + + for child in dialog.body.children: + prop_name = child.children[1].jans_name + prop_val = child.children[1].content.buffer.text + config_cli.config['DEFAULT'][prop_name] = prop_val + config_cli.write_config() + + config_cli.host = config_cli.config['DEFAULT']['jans_host'] + config_cli.client_id = config_cli.config['DEFAULT']['jca_client_id'] + config_cli.client_secret = config_cli.config['DEFAULT']['jca_client_secret'] + + def jans_creds_dialog(self, *params): + + # body=HSplit([ + # self.getTitledText("Hostname", name='jans_host', value=config_cli.host or '', jans_help="FQN name of Jannsen Config Api Server",), + # self.getTitledText("Client ID", name='jca_client_id', value=config_cli.client_id or '', jans_help="Jannsen Config Api Client ID"), + # self.getTitledText("Client Secret", name='jca_client_secret', value=config_cli.client_secret or '', jans_help="Jannsen Config Api Client Secret") + # ]) + body=HSplit([ + self.getTitledText("Hostname", name='jans_host', value=config_cli.host or 'c1.gluu.org', jans_help="FQN name of Jannsen Config Api Server",), + self.getTitledText("Client ID", name='jca_client_id', value=config_cli.client_id or '2000.2efa2c06-f711-42d6-a02c-4dcb5191669c', jans_help="Jannsen Config Api Client ID"), + self.getTitledText("Client Secret", name='jca_client_secret', value=config_cli.client_secret or 'd6yHY6lUJMgl', jans_help="Jannsen Config Api Client Secret") + ]) + buttons = [Button("Save", handler=self.save_creds)] + dialog = JansGDialog(parent=self,title="Janssen Config Api Client Credidentials", body=body, buttons=buttons) + + async def coroutine(): + app = get_app() + focused_before = app.layout.current_window + self.layout.focus(dialog) + result = await self.show_dialog_as_float(dialog) + try: + app.layout.focus(focused_before) + except: + app.layout.focus(self.center_frame) + + self.create_cli() + + ensure_future(coroutine()) def edit_client_dialog(self, **params): From 49885e76c98f324fabdbb71c6a272f25df4f00f9 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Wed, 17 Aug 2022 11:13:25 +0300 Subject: [PATCH 014/364] Revert "hopa(16-8-2022)" This reverts commit 4ee20dcf74c3f2bf5b30c72d6efd5b7d4643ed3a. --- jans-cli-tui/cli/config_cli.py | 20 +- jans-cli-tui/cli/jca.yaml | 0 jans-cli-tui/cli/pylib/__init__.py | 0 jans-cli-tui/cli/pylib/tabulate/LICENSE | 0 jans-cli-tui/cli/pylib/tabulate/README | 0 jans-cli-tui/cli/pylib/tabulate/README.md | 0 jans-cli-tui/cli/pylib/tabulate/__init__.py | 0 jans-cli-tui/cli/pylib/tabulate/tabulate.py | 0 jans-cli-tui/cli/swagger_yaml.json | 0 jans-cli-tui/cli_style.py | 2 +- jans-cli-tui/depug_hopa2.txt | 108 --------- jans-cli-tui/jans-cli-tui.py | 214 +++++------------ jans-cli-tui/models/oauth.py | 226 +++--------------- jans-cli-tui/static.py | 0 jans-cli-tui/test_logstring.py | 73 ------ jans-cli-tui/wrapper_test.py | 0 .../wui_components/edit_client_dialog.py | 1 + .../wui_components/edit_scope_dialog.py | 1 + .../wui_components/jans_cli_dialog.py | 23 +- jans-cli-tui/wui_components/jans_dialog.py | 1 + .../wui_components/jans_dialog_with_nav.py | 1 + jans-cli-tui/wui_components/jans_nav_bar.py | 2 +- .../wui_components/jans_side_nav_bar.py | 2 +- .../wui_components/jans_vetrical_nav.py | 15 +- 24 files changed, 137 insertions(+), 552 deletions(-) mode change 100755 => 100644 jans-cli-tui/cli/jca.yaml mode change 100755 => 100644 jans-cli-tui/cli/pylib/__init__.py mode change 100755 => 100644 jans-cli-tui/cli/pylib/tabulate/LICENSE mode change 100755 => 100644 jans-cli-tui/cli/pylib/tabulate/README mode change 100755 => 100644 jans-cli-tui/cli/pylib/tabulate/README.md mode change 100755 => 100644 jans-cli-tui/cli/pylib/tabulate/__init__.py mode change 100755 => 100644 jans-cli-tui/cli/pylib/tabulate/tabulate.py mode change 100755 => 100644 jans-cli-tui/cli/swagger_yaml.json mode change 100755 => 100644 jans-cli-tui/cli_style.py delete mode 100644 jans-cli-tui/depug_hopa2.txt mode change 100755 => 100644 jans-cli-tui/models/oauth.py mode change 100755 => 100644 jans-cli-tui/static.py delete mode 100644 jans-cli-tui/test_logstring.py mode change 100755 => 100644 jans-cli-tui/wrapper_test.py mode change 100755 => 100644 jans-cli-tui/wui_components/edit_client_dialog.py mode change 100755 => 100644 jans-cli-tui/wui_components/edit_scope_dialog.py mode change 100755 => 100644 jans-cli-tui/wui_components/jans_cli_dialog.py mode change 100755 => 100644 jans-cli-tui/wui_components/jans_dialog.py mode change 100755 => 100644 jans-cli-tui/wui_components/jans_dialog_with_nav.py mode change 100755 => 100644 jans-cli-tui/wui_components/jans_nav_bar.py mode change 100755 => 100644 jans-cli-tui/wui_components/jans_side_nav_bar.py mode change 100755 => 100644 jans-cli-tui/wui_components/jans_vetrical_nav.py diff --git a/jans-cli-tui/cli/config_cli.py b/jans-cli-tui/cli/config_cli.py index 4214670cc40..ce5f0c8db97 100755 --- a/jans-cli-tui/cli/config_cli.py +++ b/jans-cli-tui/cli/config_cli.py @@ -655,6 +655,12 @@ def get_device_verification_code(self): return response + def raise_error(self, msg): + if not self.wrapped: + msg = self.colored_text(msg, error_color) + raise ValueError(msg) + + def get_jwt_access_token(self, device_verified=None): @@ -667,8 +673,7 @@ def get_jwt_access_token(self, device_verified=None): response = self.get_device_verification_code() if response.status_code != 200: msg = "Unable to get device authorization user code: {}".format(response.reason) - raise ValueError( - self.colored_text(msg, error_color)) + self.raise_error(msg) result = response.json() @@ -684,7 +689,7 @@ def get_jwt_access_token(self, device_verified=None): else: msg = "Unable to get device authorization user code" - raise ValueError(self.colored_text(msg)) + self.raise_error(msg) else: result = device_verified @@ -708,8 +713,7 @@ def get_jwt_access_token(self, device_verified=None): ) self.log_response(response) if response.status_code != 200: - raise ValueError( - self.colored_text("Unable to get access token")) + self.raise_error("Unable to get access token") result = response.json() @@ -728,8 +732,7 @@ def get_jwt_access_token(self, device_verified=None): ) self.log_response(response) if response.status_code != 200: - raise ValueError( - self.colored_text("Unable to get access token")) + self.raise_error("Unable to get access token") result = response.text @@ -748,8 +751,7 @@ def get_jwt_access_token(self, device_verified=None): ) self.log_response(response) if response.status_code != 200: - raise ValueError( - self.colored_text("Unable to get access token")) + self.raise_error("Unable to get access token") result = response.json() diff --git a/jans-cli-tui/cli/jca.yaml b/jans-cli-tui/cli/jca.yaml old mode 100755 new mode 100644 diff --git a/jans-cli-tui/cli/pylib/__init__.py b/jans-cli-tui/cli/pylib/__init__.py old mode 100755 new mode 100644 diff --git a/jans-cli-tui/cli/pylib/tabulate/LICENSE b/jans-cli-tui/cli/pylib/tabulate/LICENSE old mode 100755 new mode 100644 diff --git a/jans-cli-tui/cli/pylib/tabulate/README b/jans-cli-tui/cli/pylib/tabulate/README old mode 100755 new mode 100644 diff --git a/jans-cli-tui/cli/pylib/tabulate/README.md b/jans-cli-tui/cli/pylib/tabulate/README.md old mode 100755 new mode 100644 diff --git a/jans-cli-tui/cli/pylib/tabulate/__init__.py b/jans-cli-tui/cli/pylib/tabulate/__init__.py old mode 100755 new mode 100644 diff --git a/jans-cli-tui/cli/pylib/tabulate/tabulate.py b/jans-cli-tui/cli/pylib/tabulate/tabulate.py old mode 100755 new mode 100644 diff --git a/jans-cli-tui/cli/swagger_yaml.json b/jans-cli-tui/cli/swagger_yaml.json old mode 100755 new mode 100644 diff --git a/jans-cli-tui/cli_style.py b/jans-cli-tui/cli_style.py old mode 100755 new mode 100644 index ab728d6ec61..d1ec76f0376 --- a/jans-cli-tui/cli_style.py +++ b/jans-cli-tui/cli_style.py @@ -18,4 +18,4 @@ "textarea":"nounderline bg:#ffffff fg:#d1c0c0 #ff0000", "checkbox":"nounderline bg:#ffffff fg:#d1c0c0 #ff0000", } -) \ No newline at end of file +) diff --git a/jans-cli-tui/depug_hopa2.txt b/jans-cli-tui/depug_hopa2.txt deleted file mode 100644 index 51e44947a25..00000000000 --- a/jans-cli-tui/depug_hopa2.txt +++ /dev/null @@ -1,108 +0,0 @@ - - - -{'dn': 'inum=1800.6c5faab4-dc28-4d46-94f4-df320f7c9780, -ou=clients,o=jans', - 'deletable': False, - 'clientSecret': 'oVSD1QqiLWRW', - 'frontChannelLogoutSessionRequired': False, - 'redirectUris': ['https://c1.gluu.org/admin-ui', 'http://localhost:4100'], - 'responseTypes': ['code'], - 'grantTypes': ['authorization_code', 'refresh_token', 'client_credentials'], - 'applicationType': 'web', - 'clientName': {'values': {'': 'Jans Config Api Client'}, - 'languageTags': [''], - 'value': 'Jans Config Api Client'}, - 'logoUri': {}, - 'clientUri': {}, - 'policyUri': {}, - 'tosUri': {}, - 'subjectType': 'pairwise', - 'idTokenSignedResponseAlg': 'RS256', - 'tokenEndpointAuthMethod': 'client_secret_basic', - 'requireAuthTime': False, - 'scopes': ['inum=C4F7,ou=scopes,o=jans', - 'inum=1200.1F13DB,ou=scopes,o=jans', - 'inum=1200.E0B4C8,ou=scopes,o=jans', - 'inum=1800.590346,ou=scopes,o=jans', - 'inum=1800.507006,ou=scopes,o=jans', - 'inum=1800.7866C7,ou=scopes,o=jans', - 'inum=1800.A1D5CD,ou=scopes,o=jans', - 'inum=1800.0714D9,ou=scopes,o=jans', - 'inum=1800.32C574,ou=scopes,o=jans', - 'inum=1800.C1E748,ou=scopes,o=jans', - 'inum=1800.19E881,ou=scopes,o=jans', - 'inum=1800.913FF3,ou=scopes,o=jans', - 'inum=1800.327D46,ou=scopes,o=jans', - 'inum=1800.99C0D3,ou=scopes,o=jans', - 'inum=1800.BBD836,ou=scopes,o=jans', - 'inum=1800.F8AAB3,ou=scopes,o=jans', - 'inum=1800.E03CFE,ou=scopes,o=jans', - 'inum=1800.7DC04D,ou=scopes,o=jans', - 'inum=1800.1DA5BA,ou=scopes,o=jans', - 'inum=1800.926A52,ou=scopes,o=jans', - 'inum=1800.FF17D8,ou=scopes,o=jans', - 'inum=1800.A650F6,ou=scopes,o=jans', - 'inum=1800.580BA9,ou=scopes,o=jans', - 'inum=1800.DC882D,ou=scopes,o=jans', - 'inum=1800.F97827,ou=scopes,o=jans', - 'inum=1800.8FB9E4,ou=scopes,o=jans', - 'inum=1800.0EE56D,ou=scopes,o=jans', - 'inum=1800.FD8734,ou=scopes,o=jans', - 'inum=1800.73E5B5,ou=scopes,o=jans', - 'inum=1800.0DE56F,ou=scopes,o=jans', - 'inum=1800.F0C44A,ou=scopes,o=jans', - 'inum=1800.70AA30,ou=scopes,o=jans', - 'inum=1800.632780,ou=scopes,o=jans', - 'inum=1800.2646DC,ou=scopes,o=jans', - 'inum=1800.54387F,ou=scopes,o=jans', - 'inum=1800.16B85E,ou=scopes,o=jans', - 'inum=1800.565A62,ou=scopes,o=jans', - 'inum=1800.065E1A,ou=scopes,o=jans', - 'inum=1800.4E54A5,ou=scopes,o=jans', - 'inum=1800.50BBFE,ou=scopes,o=jans', - 'inum=1800.6A2BD2,ou=scopes,o=jans', - 'inum=1800.B9BE21,ou=scopes,o=jans', - 'inum=1800.A8FD82,ou=scopes,o=jans', - 'inum=1800.BDA282,ou=scopes,o=jans', - 'inum=1800.0B7536,ou=scopes,o=jans', - 'inum=1800.8100D9,ou=scopes,o=jans', - 'inum=1800.987C52,ou=scopes,o=jans', - 'inum=1800.DB17D4,ou=scopes,o=jans', - 'inum=1800.AA15F1,ou=scopes,o=jans', - 'inum=1800.5B28B1,ou=scopes,o=jans', - 'inum=1800.53B808,ou=scopes,o=jans', - 'inum=1800.573EBC,ou=scopes,o=jans', - 'inum=2762b464-0b67-4071-8072-91cb371895a6, - ou=scopes,o=jans', - 'inum=378ac51e-6746-4b2c-a9dc-ca3261f998e1, - ou=scopes,o=jans', - 'inum=478e15ec-1871-4314-a909-3d2c84bd09f9, - ou=scopes,o=jans'], - 'trustedClient': False, - 'persistClientAuthorizations': True, - 'includeClaimsInIdToken': False, - 'customAttributes': [{'name': 'displayName', - 'multiValued': False, - 'values': ['Jans Config Api Client'], - 'displayValue': 'Jans Config Api Client', - 'value': 'Jans Config Api Client'}], - 'customObjectClasses': ['top'], - 'rptAsJwt': False, - 'accessTokenAsJwt': False, - 'accessTokenSigningAlg': 'RS256', - 'disabled': False, - 'attributes': {'runIntrospectionScriptBeforeAccessTokenAsJwtCreationAndIncludeClaims': False, - 'keepClientAuthorizationAfterExpiration': False, - 'allowSpontaneousScopes': False, - 'backchannelLogoutSessionRequired': False, - 'parLifetime': 600, - 'requirePar': False, - 'jansDefaultPromptLogin': False}, - 'displayName': 'Jans Config Api Client', - 'authenticationMethod': 'client_secret_basic', - 'tokenBindingSupported': False, - 'baseDn': 'inum=1800.6c5faab4-dc28-4d46-94f4-df320f7c9780, - ou=clients,o=jans', - 'inum': '1800.6c5faab4-dc28-4d46-94f4-df320f7c9780' - } diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index eda48ead65c..d722f52ff25 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -6,6 +6,7 @@ from shutil import get_terminal_size import time from asyncio import Future, ensure_future +from pynput.keyboard import Key, Controller import prompt_toolkit from prompt_toolkit.application import Application @@ -21,7 +22,6 @@ VerticalAlign, DynamicContainer, FloatContainer, - Window ) from prompt_toolkit.layout.containers import VerticalAlign from prompt_toolkit.layout.dimension import D @@ -89,7 +89,7 @@ def __init__(self): self.app_started = False self.width, self.height = get_terminal_size() self.app = get_app() - self.show_dialog = False ## ## ## ## + self.show_dialog = False self.set_keybindings() self.containers = {} # -------------------------------------------------------------------------------- # @@ -98,10 +98,7 @@ def __init__(self): self.active_dialog_select = '' self.Auth_clients_tabs = {} - # ----------------------------------------------------------------------------- # - self.check_jans_cli_ini() - # ----------------------------------------------------------------------------- # - + self.keyboard = Controller() self.yes_button = Button(text="Yes", handler=accept_yes) self.no_button = Button(text="No", handler=accept_no) @@ -160,7 +157,18 @@ def __init__(self): # Since first module is oauth, set center frame to my oauth main container. self.oauth_set_center_frame() + + # ----------------------------------------------------------------------------- # + self.check_jans_cli_ini() + # ----------------------------------------------------------------------------- # + + + def press_tab(self): + self.keyboard.press(Key.tab) + self.keyboard.release(Key.tab) + def create_cli(self): + conn_ok = False test_client = config_cli.client_id if config_cli.test_client else None self.cli_object = config_cli.JCA_CLI( host=config_cli.host, @@ -172,12 +180,14 @@ def create_cli(self): status = self.cli_object.check_connection() + self.press_tab() + if status is not True: buttons = [Button("OK", handler=self.jans_creds_dialog)] self.show_message("Error getting Connection Config Api", status, buttons=buttons) else: if not test_client and not self.cli_object.access_token: - + response = self.cli_object.get_device_verification_code() result = response.json() @@ -186,7 +196,7 @@ def create_cli(self): ) body = HSplit([Label(msg)]) - dialog = JansGDialog(parent=self,title="Waiting Response", body=body) + dialog = JansGDialog(title="Waiting Response", body=body) async def coroutine(): app = get_app() @@ -197,11 +207,17 @@ async def coroutine(): app.layout.focus(focused_before) except: app.layout.focus(self.center_frame) - - self.cli_object.get_jwt_access_token(result) + try: + self.cli_object.get_jwt_access_token(result) + except Exception as e: + err_dialog = JansGDialog(title="Error!", body=HSplit([Label(str(e))])) + await self.show_dialog_as_float(err_dialog) + self.create_cli() ensure_future(coroutine()) + #if not conn_ok: + # self.create_cli() def check_jans_cli_ini(self): if not(config_cli.host and (config_cli.client_id and config_cli.client_secret or config_cli.access_token)): @@ -218,7 +234,7 @@ def dialog_back_but(self): ## BACK def prapare_dialogs(self): self.data_show_client_dialog = Label(text='Selected Line Data as Json') - self.dialog_width = int(self.width*0.9) # 120 ## to be dynamic + self.dialog_width = int(self.width*0.9) # 120 ## to be dynamic self.dialog_height = int(self.height*0.8) ## to be dynamic @@ -236,85 +252,38 @@ def set_keybindings(self): self.bindings.add("tab")(self.focus_next) self.bindings.add("s-tab")(self.focus_previous) self.bindings.add("c-c")(do_exit) - # ----------------------------------------------------------------- # - def handel_long_string (self,text,values,cb): - lines = [] - if len(text) > 20 : - title_list=text.split(' ') - dum = '' - for i in range(len(title_list)): - if len(dum) < 20 : - if len(title_list[i] + dum) < 30 : - dum+=title_list[i] +' ' - else : - lines.append(dum.strip()) - dum = title_list[i] + ' ' - else : - lines.append(dum.strip()) - dum = title_list[i] + ' ' - lines.append(dum) - num_lines = len(lines) - width = len(max(lines, key=len)) - else: - width = len(text) - lines.append(text) - num_lines = len(lines) - - new_title,title_lines = '\n'.join(lines) , num_lines - - - if title_lines <= len(values) : ### if num of values (value lines) < = title_lines - lines_under_value = 0 - else : - lines_under_value = abs(title_lines-len(values)) - - if lines_under_value !=0 : - cd = HSplit([ - cb, - Label(text=('\n')*(lines_under_value-1)) - ]) - else : - cd = cb - - if title_lines <= len(values) : ### if num of values (value lines) < = title_lines - lines_under_title = abs(len(values) - title_lines) - else : - lines_under_title = 0 - - # first one >> solved >> title - if lines_under_title >=1 : - for i in range(lines_under_title): - new_title +='\n' - else : - pass - - return new_title , cd , width - - def getTitledText(self, title, name, value='', height=1, jans_help='', width=None,style=''): + def getTitledText(self, title, name, value='', height=1, jans_help='', width=None): multiline = height > 1 - ta = TextArea(text=value, multiline=multiline,style="class:titledtext") + ta = TextArea(text=value, multiline=multiline, style='class:textarea') ta.window.jans_name = name ta.window.jans_help = jans_help - li,cd,width = self.handel_long_string(title,[1]*height,ta) + if width: + ta.window.width = width + li = title + for i in range(height-1): + li +='\n' - return VSplit([Label(text=li, width=width,style=style), cd], padding=1) - - def getTitledCheckBox(self, title, name, values,style=''): - cb = CheckboxList(values=[(o,o) for o in values]) - cb.window.jans_name = name - li,cd,width = self.handel_long_string(title,values,cb) + return VSplit([Label(text=li + ':', width=len(title)+1), ta], height=height, padding=1) - return VSplit([Label(text=li, width=width,style=style,wrap_lines=False), cd]) + def getTitledCheckBox(self, title, name, values): + cb = CheckboxList(values=[(o,o) for o in values],) + cb.window.jans_name = name + li = title + for i in range(len(values)-1): + li +='\n' + return VSplit([Label(text=li, width=len(title)+1), cb] ) - def getTitledRadioButton(self, title, name, values,style=''): + def getTitledRadioButton(self, title, name, values): rl = RadioList(values=[(option, option) for option in values]) rl.window.jans_name = name - li,rl2,width = self.handel_long_string(title,values,rl) - - return VSplit([Label(text=li, width=width,style=style), rl2],) + li = title + for i in range(len(values)-1): + li +='\n' - # ----------------------------------------------------------------- # + return VSplit([Label(text=li, width=len(title)+1), rl], + height=len(values) + ) def getButton(self, text, name, jans_help, handler=None): b = Button(text=text, width=len(text)+2) @@ -381,6 +350,8 @@ async def coroutine(): ensure_future(coroutine()) + + def data_display_dialog(self, **params): body = HSplit([ @@ -394,7 +365,7 @@ def data_display_dialog(self, **params): ) ]) - dialog = JansGDialog(parent=self,title=params['selected'][0], body=body) + dialog = JansGDialog(title=params['selected'][0], body=body) self.show_jans_dialog(dialog) @@ -410,26 +381,23 @@ def save_creds(self, dialog): config_cli.client_id = config_cli.config['DEFAULT']['jca_client_id'] config_cli.client_secret = config_cli.config['DEFAULT']['jca_client_secret'] + def jans_creds_dialog(self, *params): - # body=HSplit([ - # self.getTitledText("Hostname", name='jans_host', value=config_cli.host or '', jans_help="FQN name of Jannsen Config Api Server",), - # self.getTitledText("Client ID", name='jca_client_id', value=config_cli.client_id or '', jans_help="Jannsen Config Api Client ID"), - # self.getTitledText("Client Secret", name='jca_client_secret', value=config_cli.client_secret or '', jans_help="Jannsen Config Api Client Secret") - # ]) body=HSplit([ - self.getTitledText("Hostname", name='jans_host', value=config_cli.host or 'c1.gluu.org', jans_help="FQN name of Jannsen Config Api Server",), - self.getTitledText("Client ID", name='jca_client_id', value=config_cli.client_id or '2000.2efa2c06-f711-42d6-a02c-4dcb5191669c', jans_help="Jannsen Config Api Client ID"), - self.getTitledText("Client Secret", name='jca_client_secret', value=config_cli.client_secret or 'd6yHY6lUJMgl', jans_help="Jannsen Config Api Client Secret") + self.getTitledText("Hostname", name='jans_host', value=config_cli.host or '', jans_help="FQN name of Jannsen Config Api Server"), + self.getTitledText("Client ID", name='jca_client_id', value=config_cli.client_id or '', jans_help="Jannsen Config Api Client ID"), + self.getTitledText("Client Secret", name='jca_client_secret', value=config_cli.client_secret or '', jans_help="Jannsen Config Api Client Secret") ]) + buttons = [Button("Save", handler=self.save_creds)] - dialog = JansGDialog(parent=self,title="Janssen Config Api Client Credidentials", body=body, buttons=buttons) + dialog = JansGDialog(title="Janssen Config Api Client Credidentials", body=body, buttons=buttons) async def coroutine(): app = get_app() focused_before = app.layout.current_window self.layout.focus(dialog) - result = await self.show_dialog_as_float(dialog) + result = await self.show_dialog_as_float(dialog) try: app.layout.focus(focused_before) except: @@ -439,72 +407,22 @@ async def coroutine(): ensure_future(coroutine()) - def edit_client_dialog(self, **params): - navBar = JansSideNavBar(myparent=self, - entries=list( - self.oauth_tabs['clients'].keys()), - selection_changed=( - self.client_dialog_nav_selection_changed), - select=0, - entries_color='#2600ff') - selected_line_data = params['data'] ## if we want to makeuse of it - body = VSplit([ - HSplit( - [ - navBar - ], - width=30 - ), - Window(width=1, char="|",), - HSplit( - [ - DynamicContainer( - lambda: self.oauth_tabs['clients'][self.oauth_dialog_nav]) - ],width=D()), - ], height=32) - dialog = JansGDialog(parent=self,title="Edit user Data (Clients)", body=body) + + def edit_client_dialog(self, **params): + dialog = EditClientDialog(self,**params) self.show_jans_dialog(dialog) def edit_scope_dialog(self, **params): - - navBar = JansSideNavBar(myparent=self, - entries=list( - self.oauth_tabs['clients'].keys()), - selection_changed=( - self.client_dialog_nav_selection_changed), - select=0, - entries_color='#2600ff') - - selected_line_data = params['data'] ## if we want to makeuse of it - - body = VSplit([ - HSplit( - [ - navBar - ], - width=30 - ), - Window(width=1, char="|",), - HSplit( - [ - DynamicContainer( - lambda: self.oauth_tabs['clients'][self.oauth_dialog_nav]) - ]), - ], height=32) - - dialog = JansGDialog(self,title="Edit Scope Data (Clients)", body=body) + dialog = EditScopeDialog(self,**params) self.show_jans_dialog(dialog) def show_message(self, title, message, buttons=[]): body = HSplit([Label(message)]) - dialog = JansGDialog(parent=self,title=title, body=body, buttons=buttons) + dialog = JansGDialog(title=title, body=body, buttons=buttons) self.show_jans_dialog(dialog) - def show_again(self): ## nasted dialog Button - self.show_message("Again", "Nasted Dialogs",) - - + application = JansCliApp() def run(): @@ -513,4 +431,4 @@ def run(): if __name__ == "__main__": - run() \ No newline at end of file + run() diff --git a/jans-cli-tui/models/oauth.py b/jans-cli-tui/models/oauth.py old mode 100755 new mode 100644 index 4c44066de34..8ca4a5664ba --- a/jans-cli-tui/models/oauth.py +++ b/jans-cli-tui/models/oauth.py @@ -57,205 +57,57 @@ def oauth_prepare_tabs(self): self.oauth_tabs = {} self.oauth_tabs['clients'] = OrderedDict() - self.oauth_tabs['clients']['Basic'] = HSplit([ + self.oauth_tabs['clients']['Person Authentication'] = HSplit([ VSplit([ - self.getTitledText("Client_ID", name='Client_ID',style='green'), + # Label(text=str('Client_ID')), + #TextArea(text='', name='Client_ID'), + self.getTitledText("Client_ID", name='Client_ID'), Window(width=1, char=" ",), - self.getTitledCheckBox("Active:", name='active', values=['active'],style='green'), + self.getTitledCheckBox("Active:", name='active', values=['active']), ]) , - self.getTitledText("Client Name", name='displayName',style='green'), - self.getTitledText("Client Secret", name='clientSecret',style='green'), - self.getTitledText("Description", name='Description',style='green'), - self.getTitledRadioButton("Id_token Subject Type:", name='Id_token', values=['Pairwise','Public'],style='green'), - self.getTitledCheckBox("Grant:", name='Grant', values=['Authorizatuin Code', 'Refresh Token', 'UMA Ticket','Client Credential','Password','Implicit'],style='green'), - self.getTitledCheckBox("Response Types:", name='responseTypes', values=['code', 'token', 'id_token'],style='green'), - self.getTitledCheckBox("Supress Authorization:", name='Supress', values=['True'],style='green'), - self.getTitledRadioButton("Application Type:", name='applicationType', values=['web', 'native'],style='green'), - self.getTitledText("Redirect Uris", name='redirectUris', height=3,style='green'), - self.getTitledText("Redirect Regex", name='redirectregex',style='green'), - self.getTitledText("Scopes", name='scopes', height=3,style='green'), - # HSplit([Box(body=VSplit([self.yes_button, self.no_button], align="CENTER", padding=3), style="class:button-bar", height=1)], - # height=D(), - # align = VerticalAlign.BOTTOM, - # ) - ],width=D(), - ) - - self.oauth_tabs['clients']['Tokens'] = HSplit([ - self.getTitledRadioButton("Access Token Type", name='access_token_type', values=['JWT','Reference'],style='green'), - self.getTitledCheckBox("Incliude Claims in id_token", name='id_token_claims', values=['True'],style='green'), - self.getTitledCheckBox("Run introspection script before JWT access token creation", name='Supress_JWT', values=['True'],style='green'), - self.getTitledText("Token binding confirmation method for id_token", name='id_token_binding_confirmation',style='green'), - self.getTitledText("Access token additional audiences", name='access_token_audiences',style='green'), - VSplit([ - Button("+", handler=self.show_again,left_symbol='[',right_symbol=']',width=3) - ]), - self.getTitledText("Access token lifetime", name='access_token_lifetime',style='green'), - self.getTitledText("Refresh token lifetime", name='refresh_token_lifetime',style='green'), - self.getTitledText("Defult max authn age", name='max_authn_age',style='green'), - - ],width=D()) - - self.oauth_tabs['clients']['Logout'] = HSplit([ - - self.getTitledText("Front channel logout URI", name='f_channel_logout_URI',style='green'), - self.getTitledText("Post logout redirect URI", name='p_channel_logout_redirect_URI',style='green'), - self.getTitledText("Back channel logout URI", name='b_channel_logout_URI',style='green'), - self.getTitledCheckBox("Back channel logout session required", name='b_channel_session_required', values=['True'],style='green'), - self.getTitledCheckBox("Front channel logout session required", name='f_channel_session_required', values=['True'],style='green'), - - ],width=D() - ) - - self.oauth_tabs['clients']['Software Info'] = HSplit([ - self.getTitledText("Client URI", name='client_URI',style='green'), - self.getTitledText("Policy URI", name='policy_URI',style='green'), - self.getTitledText("Logo URI", name='logo_URI',style='green'), - self.getTitledText("Term of service URI", name='term_of_service_URI',style='green'), - self.getTitledText("Contacts", name='contacts',style='green'), - VSplit([ - Button("+", handler=self.show_again,left_symbol='[',right_symbol=']',width=3,) - ]), - self.getTitledText("Authorized JS origins", name='authorized_JS_origins',style='green'), - VSplit([ - Button("+", handler=self.show_again,left_symbol='[',right_symbol=']',width=3) - ]), - self.getTitledText("Software id", name='software_id',style='green'), - self.getTitledText("Software version", name='software_version',style='green'), - self.getTitledText("Software statement", name='software_statement',style='green'), - - ],width=D()) + self.getTitledText("Client Name:", name='displayName'), + self.getTitledText("Client Secret:", name='clientSecret'), + self.getTitledText("Description:", name='Description'), + self.getTitledRadioButton("Id_token Subject Type:", name='Id_token', values=['Pairwise','Public'],), + self.getTitledCheckBox("Grant:", name='Grant', values=['Authorizatuin Code', 'Refresh Token', 'UMA Ticket','Client Credential','Password','Implicit']), + self.getTitledCheckBox("Response Types:", name='responseTypes', values=['code', 'token', 'id_token']), + self.getTitledCheckBox("Supress Authorization:", name='Supress', values=['True']), + self.getTitledRadioButton("Application Type:", name='applicationType', values=['web', 'native']), + self.getTitledText("Redirect Uris:", name='redirectUris', height=3), + self.getTitledText("Redirect Regex:", name='redirectregex'), + self.getTitledText("Scopes:", name='scopes', height=3), - self.oauth_tabs['clients']['CIBA/PAR/UMA'] = HSplit([ - Label(text="CIBA",style='bold'), - self.getTitledRadioButton("Token delivery method", name='applicationType', values=['poll','push', 'ping'],style='green'), - self.getTitledText("Client notification endpoint", name='displayName',style='green'), - self.getTitledCheckBox("Require user code param", name='Supress', values=['True'],style='green'), - - Label(text="PAR",style='bold'), - self.getTitledText("Request lifetime", name='displayName',style='green'), - self.getTitledCheckBox("Request PAR", name='Supress', values=['True'],style='green'), - Label("UMA",style='bold'), - self.getTitledRadioButton("PRT token type", name='applicationType', values=['JWT', 'Reference'],style='green'), - self.getTitledText("Claims redirect URI", name='displayName',style='green'), - - Label(text="Dropdown 1",style='blue'), - Label(text="Dropdown 2",style='blue'), - Label(text="tabel",style='blue'), ## TODO with Jans VerticalNav - - # JansVerticalNav( - # myparent=self, - # headers=['Client ID', 'Client Name', 'Grant Types', 'Subject Type'], - # preferred_size= [0,0,30,0], - # data=[['1','2','3','4'],['1','2','3','4'],['1','2','3','4'],['1','2','3','4'],], - # on_enter=self.edit_client_dialog, - # on_display=self.data_display_dialog, - # # selection_changed=self.data_selection_changed, - # selectes=0, - # headerColor='green', - # entriesColor='white', - # all_data=[['1','2','3','4'],['1','2','3','4'],['1','2','3','4'],['1','2','3','4'],] - # ) - ] - ) - - self.oauth_tabs['clients']['Encryption/Signing'] = HSplit([ - self.getTitledText("Client JWKS URI", name='displayName',style='green'), - self.getTitledText("Client JWKS", name='displayName',style='green'), - VSplit([ - Label(text="id_token"), - Label(text="a, b, c",style='red'), - ]), - VSplit([ - Label(text="Access token"), - Label(text="a",style='red'), - ]), - VSplit([ - Label(text="Userinfo"), - Label(text="a, b, c",style='red'), - ]), - VSplit([ - Label(text="JARM"), - Label(text="a, b, c",style='red'), - ]), - VSplit([ - Label(text="Request Object"), - Label(text="a, b, c",style='red'), - ]), - - ] + HSplit([Box(body=VSplit([self.yes_button, self.no_button], align="CENTER", padding=3), style="class:button-bar", height=1)], + height=D(), + align = VerticalAlign.BOTTOM, ) - - self.oauth_tabs['clients']['Advanced Client Properties'] = HSplit([ - - self.getTitledCheckBox("Default Prompt=login", name='Supress', values=['True'],style='green'), - VSplit([ - self.getTitledCheckBox("Persist Authorizations", name='Supress', values=['True'],style='green'), - self.getTitledCheckBox("Keep expired?", name='Supress', values=['True'],style='green'), - ]) , - self.getTitledCheckBox("Allow spontaneos scopes", name='Supress', values=['True'],style='green'), - - self.getTitledText("spontaneos scopes validation regex", name='displayName',style='green'), - VSplit([ - Label(text="Spontaneous Scopes",style='green'), - Button("view current", handler=self.show_again,left_symbol='',right_symbol='',) - - ]) , - self.getTitledText("Initial Login URI", name='displayName',style='green'), - - VSplit([ - self.getTitledText("Request URIs", name='clientSecret',style='green'), - Button("+", handler=self.show_again,left_symbol='[',right_symbol=']',width=3,) - ]) , - - Label(text="Dropdown 3",style='blue'), - - VSplit([ - self.getTitledText("Allowed ACRs", name='clientSecret',style='green'), - Button("+", handler=self.show_again,left_symbol='[',right_symbol=']',width=3,) - ]) , - self.getTitledText("TLS Subject DN", name='clientSecret',style='green'), - - VSplit([ - self.getTitledCheckBox("Client Experiation Date", name='id_token_claims', values=['True'],style='green'), - Label(text="Pick Date",style='blue'), - ]) , - - - - ],width=D() - ) - - self.oauth_tabs['clients']['Client Scripts'] = HSplit([ - Label(text="Dropdown 4",style='blue'), - Label(text="Dropdown 5",style='blue'), - Label(text="Dropdown 6",style='blue'), - Label(text="Dropdown 7",style='blue'), - Label(text="Dropdown 8",style='blue'), - ] - ) - - - self.oauth_tabs['clients']['Save'] = HSplit([ - Button("Save", handler=self.show_again,left_symbol='(',right_symbol=')',) - - ],width=D() + ], ) - - + self.oauth_tabs['clients']['Consent Gathering'] = Label(text=str('Consent Gathering')) + self.oauth_tabs['clients']['Post Authentication'] = Label(text=str('Post Authentication')) + self.oauth_tabs['clients']['id_token'] = Label(text=str('id_token')) + self.oauth_tabs['clients']['Password Grant'] = Label(text=str('Password Grant')) + self.oauth_tabs['clients']['CIBA End User Notification'] = Label(text=str('CIBA End User Notification')) + self.oauth_tabs['clients']['OpenId Configuration'] = Label(text=str('OpenId Configuration')) + self.oauth_tabs['clients']['Dynamic Scope'] = Label(text=str('Dynamic Scope')) + self.oauth_tabs['clients']['Spontaneous Scope'] = Label(text=str('Spontaneous Scope')) + self.oauth_tabs['clients']['End Session'] = Label(text=str('End Session')) + self.oauth_tabs['clients']['Client Registation'] = Label(text=str('Client Registation')) + self.oauth_tabs['clients']['Introseption'] = Label(text=str('Introseption')) + self.oauth_tabs['clients']['Update Token'] = Label(text=str('Update Token')) self.oauth_dialog_nav = list(self.oauth_tabs['clients'].keys())[0] def client_dialog_nav_selection_changed(self, selection): self.oauth_dialog_nav = selection - # def edit_client(self, selected,event,size): ## enter - # self.edit_client_dialog() - # # self.active_dialog_select = 'enter' - # # self.show_dialog = True + def edit_client(self, selected,event,size): ## enter + self.edit_client_dialog() + # self.active_dialog_select = 'enter' + # self.show_dialog = True - # # event.app.layout.focus(self.my_dialogs()) + # event.app.layout.focus(self.my_dialogs()) def oauth_prepare_containers(self): @@ -355,7 +207,7 @@ def oauth_update_clients(self): headers=['Client ID', 'Client Name', 'Grant Types', 'Subject Type'], preferred_size= [0,0,30,0], data=data, - on_enter=self.edit_client_dialog, + on_enter=self.edit_client, on_display=self.data_display_dialog, # selection_changed=self.data_selection_changed, selectes=0, @@ -430,4 +282,4 @@ def display_scope(self): pass def edit_scope(self, selected,event,size): ## enter - self.edit_scope_dialog() \ No newline at end of file + self.edit_scope_dialog() diff --git a/jans-cli-tui/static.py b/jans-cli-tui/static.py old mode 100755 new mode 100644 diff --git a/jans-cli-tui/test_logstring.py b/jans-cli-tui/test_logstring.py deleted file mode 100644 index 193b7adcdc4..00000000000 --- a/jans-cli-tui/test_logstring.py +++ /dev/null @@ -1,73 +0,0 @@ - - def handel_long_string (self,text,values,cb): - lines = [] - if len(text) > 20 : - title_list=text.split(' ') - dum = '' - for i in range(len(title_list)): - if len(dum) < 20 : - if len(title_list[i] + dum) < 30 : - dum+=title_list[i] +' ' - else : - lines.append(dum.strip()) - dum = title_list[i] + ' ' - else : - lines.append(dum.strip()) - dum = title_list[i] + ' ' - lines.append(dum) - num_lines = len(lines) - width = len(max(lines, key=len)) - else: - width = len(text) - lines.append(text) - num_lines = len(lines) - - - new_title,title_lines = '\n'.join(lines) , num_lines - - - if title_lines <= len(values) : ### if num of values (value lines) < = title_lines - lines_under_value = 0 - else : - lines_under_value = abs(title_lines-len(values)) - - if lines_under_value !=0 : - cd = HSplit([ - cb, - Label(text=('\n')*(lines_under_value-1)) - ]) - else : - cd = cb - - if title_lines <= len(values) : ### if num of values (value lines) < = title_lines - lines_under_title = abs(len(values) - title_lines) - else : - lines_under_title = 0 - - # first one >> solved >> title - if lines_under_title >=1 : - for i in range(lines_under_title): - new_title +='\n' - else : - pass - - return new_title , cd , width - - - - def getTitledCheckBox(self, title, name, values,style=''): - ## two problem here - ## first one is the space under title (if many values) - ## Sec one is the space under values (if multiline title) - - - cb = CheckboxList(values=[(o,o) for o in values],) - cb.window.jans_name = name - - li,cd,width = self.handel_long_string(title,values,cb) - - - return VSplit([Label(text=li, width=width,style=style,wrap_lines=False), cd]) - - - diff --git a/jans-cli-tui/wrapper_test.py b/jans-cli-tui/wrapper_test.py old mode 100755 new mode 100644 diff --git a/jans-cli-tui/wui_components/edit_client_dialog.py b/jans-cli-tui/wui_components/edit_client_dialog.py old mode 100755 new mode 100644 index 9df315942ab..ee47573806b --- a/jans-cli-tui/wui_components/edit_client_dialog.py +++ b/jans-cli-tui/wui_components/edit_client_dialog.py @@ -91,3 +91,4 @@ def __pt_container__(self): + diff --git a/jans-cli-tui/wui_components/edit_scope_dialog.py b/jans-cli-tui/wui_components/edit_scope_dialog.py old mode 100755 new mode 100644 index ad059cdf710..b051bc58a63 --- a/jans-cli-tui/wui_components/edit_scope_dialog.py +++ b/jans-cli-tui/wui_components/edit_scope_dialog.py @@ -91,3 +91,4 @@ def __pt_container__(self): + diff --git a/jans-cli-tui/wui_components/jans_cli_dialog.py b/jans-cli-tui/wui_components/jans_cli_dialog.py old mode 100755 new mode 100644 index 5cca3a56244..29dbff9d62b --- a/jans-cli-tui/wui_components/jans_cli_dialog.py +++ b/jans-cli-tui/wui_components/jans_cli_dialog.py @@ -8,35 +8,24 @@ from static import DialogResult from functools import partial class JansGDialog: - def __init__(self, parent, title, body, buttons=[]): - - self.result = None + def __init__(self, title, body, buttons=[]): self.future = Future() self.body = body + if not buttons: + buttons = [Button(text="OK")] + def do_handler(button_text, handler): if handler: handler(self) self.future.set_result(button_text) - def exit_me(button): - if not getattr(button, 'keep_me', False): - parent.root_layout.floats.pop() - if parent.root_layout.floats: - parent.layout.focus(parent.root_layout.floats[-1].content) - else: - parent.layout.focus(parent.center_frame) - - if not buttons: - buttons = [Button(text="OK")] - for button in buttons: - button.handler = partial(do_handler, button, button.handler) - + button.handler = partial(do_handler, button.text, button.handler) self.dialog = Dialog( title=title, - body=self.body, + body=body, buttons=buttons, width=D(preferred=80), modal=True, diff --git a/jans-cli-tui/wui_components/jans_dialog.py b/jans-cli-tui/wui_components/jans_dialog.py old mode 100755 new mode 100644 index 5cf647db1c3..91c7d5736ec --- a/jans-cli-tui/wui_components/jans_dialog.py +++ b/jans-cli-tui/wui_components/jans_dialog.py @@ -43,3 +43,4 @@ def create_window(self): #--------------------------------------------------------------------------------------# def __pt_container__(self): return self.dialog + diff --git a/jans-cli-tui/wui_components/jans_dialog_with_nav.py b/jans-cli-tui/wui_components/jans_dialog_with_nav.py old mode 100755 new mode 100644 index ed2ab622c8f..d068e9e931e --- a/jans-cli-tui/wui_components/jans_dialog_with_nav.py +++ b/jans-cli-tui/wui_components/jans_dialog_with_nav.py @@ -54,3 +54,4 @@ def create_window(self): #--------------------------------------------------------------------------------------# def __pt_container__(self): return self.dialog + diff --git a/jans-cli-tui/wui_components/jans_nav_bar.py b/jans-cli-tui/wui_components/jans_nav_bar.py old mode 100755 new mode 100644 index dee5225b94a..e91cc2be899 --- a/jans-cli-tui/wui_components/jans_nav_bar.py +++ b/jans-cli-tui/wui_components/jans_nav_bar.py @@ -60,4 +60,4 @@ def _go_up(event) -> None: self.cur_navbar_selection = (self.cur_navbar_selection + 1) % len(self.navbar_entries) self.update_status_bar() - return kb \ No newline at end of file + return kb diff --git a/jans-cli-tui/wui_components/jans_side_nav_bar.py b/jans-cli-tui/wui_components/jans_side_nav_bar.py old mode 100755 new mode 100644 index 45cf9875094..78da832d5b9 --- a/jans-cli-tui/wui_components/jans_side_nav_bar.py +++ b/jans-cli-tui/wui_components/jans_side_nav_bar.py @@ -84,4 +84,4 @@ def _(event): return kb def __pt_container__(self): - return self.side_Nav \ No newline at end of file + return self.side_Nav diff --git a/jans-cli-tui/wui_components/jans_vetrical_nav.py b/jans-cli-tui/wui_components/jans_vetrical_nav.py old mode 100755 new mode 100644 index cd45ffff15a..845f384189d --- a/jans-cli-tui/wui_components/jans_vetrical_nav.py +++ b/jans-cli-tui/wui_components/jans_vetrical_nav.py @@ -106,16 +106,16 @@ def handel_Header_spaces(self): for i in self.mod_data[dataline]: line.append(len(i)) datalen.append(line) - sp_dict = {} + dict = {} for num in range(len(datalen[0])): - sp_dict [num] = [] + dict[num] = [] for k in range(len(datalen)): for i in range(len(datalen[k])): - sp_dict [i].append(datalen[k][i]) + dict[i].append(datalen[k][i]) - for i in sp_dict : - self.spaces.append(max(sp_dict [i])) + for i in dict: + self.spaces.append(max(dict[i])) for i in range(len(self.spaces)): ## handel header collesion (when the headers length is greater that the tallest data index length) @@ -174,7 +174,8 @@ def _go_up(event) -> None: def _(event): passed = [i.strip() for i in self.data[self.selectes]] size = self.myparent.output.get_size() - self.on_enter(passed=passed,event=event,size=size,data=self.all_data[self.selectes]) + self.on_enter(passed,event,size) + @kb.add("d") def _(event): @@ -193,4 +194,4 @@ def _(event): # -------------------------------------------------------------------------------- # def __pt_container__(self): - return self.container \ No newline at end of file + return self.container From 6f3a354986ff53ab06d8694c7cbd0f740d2b918e Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Wed, 17 Aug 2022 11:13:45 +0300 Subject: [PATCH 015/364] Revert "hopa" This reverts commit 939af86eac58ae4bd6ffc1c530e12397ab8099ed. --- jans-cli-tui/jans-cli-tui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index d722f52ff25..669a5e68be3 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -89,7 +89,7 @@ def __init__(self): self.app_started = False self.width, self.height = get_terminal_size() self.app = get_app() - self.show_dialog = False + self.show_dialog = False ## ## ## ## self.set_keybindings() self.containers = {} # -------------------------------------------------------------------------------- # From 6cf5dd88f62973e594f79290a4a0cca71214e471 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Wed, 17 Aug 2022 11:20:56 +0300 Subject: [PATCH 016/364] fix: jans-cli add parent to dialog --- jans-cli-tui/jans-cli-tui.py | 10 +++++----- jans-cli-tui/wui_components/jans_cli_dialog.py | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index 669a5e68be3..f6bc5959c29 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -196,7 +196,7 @@ def create_cli(self): ) body = HSplit([Label(msg)]) - dialog = JansGDialog(title="Waiting Response", body=body) + dialog = JansGDialog(self, title="Waiting Response", body=body) async def coroutine(): app = get_app() @@ -210,7 +210,7 @@ async def coroutine(): try: self.cli_object.get_jwt_access_token(result) except Exception as e: - err_dialog = JansGDialog(title="Error!", body=HSplit([Label(str(e))])) + err_dialog = JansGDialog(self, title="Error!", body=HSplit([Label(str(e))])) await self.show_dialog_as_float(err_dialog) self.create_cli() @@ -365,7 +365,7 @@ def data_display_dialog(self, **params): ) ]) - dialog = JansGDialog(title=params['selected'][0], body=body) + dialog = JansGDialog(self, title=params['selected'][0], body=body) self.show_jans_dialog(dialog) @@ -391,7 +391,7 @@ def jans_creds_dialog(self, *params): ]) buttons = [Button("Save", handler=self.save_creds)] - dialog = JansGDialog(title="Janssen Config Api Client Credidentials", body=body, buttons=buttons) + dialog = JansGDialog(self, title="Janssen Config Api Client Credidentials", body=body, buttons=buttons) async def coroutine(): app = get_app() @@ -419,7 +419,7 @@ def edit_scope_dialog(self, **params): def show_message(self, title, message, buttons=[]): body = HSplit([Label(message)]) - dialog = JansGDialog(title=title, body=body, buttons=buttons) + dialog = JansGDialog(self, title=title, body=body, buttons=buttons) self.show_jans_dialog(dialog) diff --git a/jans-cli-tui/wui_components/jans_cli_dialog.py b/jans-cli-tui/wui_components/jans_cli_dialog.py index 29dbff9d62b..369701f5733 100644 --- a/jans-cli-tui/wui_components/jans_cli_dialog.py +++ b/jans-cli-tui/wui_components/jans_cli_dialog.py @@ -8,9 +8,10 @@ from static import DialogResult from functools import partial class JansGDialog: - def __init__(self, title, body, buttons=[]): + def __init__(self, parent, title, body, buttons=[]): self.future = Future() self.body = body + self.myparent = parent if not buttons: buttons = [Button(text="OK")] From c4d76e5bd835e34b6594c30c6fd5b2d3c1d13f87 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Wed, 17 Aug 2022 02:08:42 -0700 Subject: [PATCH 017/364] feat: jans-cli implement add handel _long_string --- jans-cli-tui/jans-cli-tui.py | 112 ++++++--- jans-cli-tui/models/oauth.py | 218 +++++++++++++++--- .../wui_components/jans_vetrical_nav.py | 12 +- 3 files changed, 266 insertions(+), 76 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index f6bc5959c29..9b7e4e62e6f 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -162,7 +162,6 @@ def __init__(self): self.check_jans_cli_ini() # ----------------------------------------------------------------------------- # - def press_tab(self): self.keyboard.press(Key.tab) self.keyboard.release(Key.tab) @@ -216,28 +215,22 @@ async def coroutine(): ensure_future(coroutine()) - #if not conn_ok: - # self.create_cli() - def check_jans_cli_ini(self): if not(config_cli.host and (config_cli.client_id and config_cli.client_secret or config_cli.access_token)): self.jans_creds_dialog() else : self.create_cli() - def dialog_back_but(self): ## BACK self.active_dialog_select = '' self.show_dialog = False self.layout.focus(self.center_frame) - def prapare_dialogs(self): self.data_show_client_dialog = Label(text='Selected Line Data as Json') self.dialog_width = int(self.width*0.9) # 120 ## to be dynamic self.dialog_height = int(self.height*0.8) ## to be dynamic - def focus_next(self, ev): focus_next(ev) self.update_status_bar() @@ -253,38 +246,85 @@ def set_keybindings(self): self.bindings.add("s-tab")(self.focus_previous) self.bindings.add("c-c")(do_exit) - def getTitledText(self, title, name, value='', height=1, jans_help='', width=None): + # ----------------------------------------------------------------- # + def handle_long_string (self,text,values,cb): + lines = [] + if len(text) > 20 : + title_list=text.split(' ') + dum = '' + for i in range(len(title_list)): + if len(dum) < 20 : + if len(title_list[i] + dum) < 30 : + dum+=title_list[i] +' ' + else : + lines.append(dum.strip()) + dum = title_list[i] + ' ' + else : + lines.append(dum.strip()) + dum = title_list[i] + ' ' + lines.append(dum) + num_lines = len(lines) + width = len(max(lines, key=len)) + else: + width = len(text) + lines.append(text) + num_lines = len(lines) + + + new_title,title_lines = '\n'.join(lines) , num_lines + + + if title_lines <= len(values) : ### if num of values (value lines) < = title_lines + lines_under_value = 0 + else : + lines_under_value = abs(title_lines-len(values)) + + if lines_under_value !=0 : + cd = HSplit([ + cb, + Label(text=('\n')*(lines_under_value-1)) + ]) + else : + cd = cb + + if title_lines <= len(values) : ### if num of values (value lines) < = title_lines + lines_under_title = abs(len(values) - title_lines) + else : + lines_under_title = 0 + + # first one >> solved >> title + if lines_under_title >=1 : + for i in range(lines_under_title): + new_title +='\n' + else : + pass + + return new_title , cd , width + + def getTitledText(self, title, name, value='', height=1, jans_help='', width=None,style=''): multiline = height > 1 - ta = TextArea(text=value, multiline=multiline, style='class:textarea') + ta = TextArea(text=value, multiline=multiline,style="class:titledtext") ta.window.jans_name = name ta.window.jans_help = jans_help - if width: - ta.window.width = width - li = title - for i in range(height-1): - li +='\n' + li,cd,width = self.handle_long_string(title,[1]*height,ta) - return VSplit([Label(text=li + ':', width=len(title)+1), ta], height=height, padding=1) - - def getTitledCheckBox(self, title, name, values): - cb = CheckboxList(values=[(o,o) for o in values],) + return VSplit([Label(text=li, width=width,style=style), cd], padding=1) + + def getTitledCheckBox(self, title, name, values,style=''): + cb = CheckboxList(values=[(o,o) for o in values]) cb.window.jans_name = name - li = title - for i in range(len(values)-1): - li +='\n' - return VSplit([Label(text=li, width=len(title)+1), cb] ) + li,cd,width = self.handle_long_string(title,values,cb) + + return VSplit([Label(text=li, width=width,style=style,wrap_lines=False), cd]) - def getTitledRadioButton(self, title, name, values): + def getTitledRadioButton(self, title, name, values,style=''): rl = RadioList(values=[(option, option) for option in values]) rl.window.jans_name = name - li = title - for i in range(len(values)-1): - li +='\n' - - return VSplit([Label(text=li, width=len(title)+1), rl], - height=len(values) - ) + li,rl2,width = self.handle_long_string(title,values,rl) + + return VSplit([Label(text=li, width=width,style=style), rl2],) + # ----------------------------------------------------------------- # def getButton(self, text, name, jans_help, handler=None): b = Button(text=text, width=len(text)+2) b.window.jans_name = name @@ -350,8 +390,6 @@ async def coroutine(): ensure_future(coroutine()) - - def data_display_dialog(self, **params): body = HSplit([ @@ -381,7 +419,6 @@ def save_creds(self, dialog): config_cli.client_id = config_cli.config['DEFAULT']['jca_client_id'] config_cli.client_secret = config_cli.config['DEFAULT']['jca_client_secret'] - def jans_creds_dialog(self, *params): body=HSplit([ @@ -407,8 +444,6 @@ async def coroutine(): ensure_future(coroutine()) - - def edit_client_dialog(self, **params): dialog = EditClientDialog(self,**params) self.show_jans_dialog(dialog) @@ -422,7 +457,12 @@ def show_message(self, title, message, buttons=[]): dialog = JansGDialog(self, title=title, body=body, buttons=buttons) self.show_jans_dialog(dialog) - + + def show_again(self): ## nasted dialog Button + self.show_message("Again", "Nasted Dialogs",) + + + application = JansCliApp() def run(): diff --git a/jans-cli-tui/models/oauth.py b/jans-cli-tui/models/oauth.py index 8ca4a5664ba..4f49cee2a46 100644 --- a/jans-cli-tui/models/oauth.py +++ b/jans-cli-tui/models/oauth.py @@ -57,48 +57,198 @@ def oauth_prepare_tabs(self): self.oauth_tabs = {} self.oauth_tabs['clients'] = OrderedDict() - self.oauth_tabs['clients']['Person Authentication'] = HSplit([ + self.oauth_tabs['clients']['Basic'] = HSplit([ VSplit([ - # Label(text=str('Client_ID')), - #TextArea(text='', name='Client_ID'), - self.getTitledText("Client_ID", name='Client_ID'), + self.getTitledText("Client_ID", name='Client_ID',style='green'), Window(width=1, char=" ",), - self.getTitledCheckBox("Active:", name='active', values=['active']), + self.getTitledCheckBox("Active:", name='active', values=['active'],style='green'), ]) , - self.getTitledText("Client Name:", name='displayName'), - self.getTitledText("Client Secret:", name='clientSecret'), - self.getTitledText("Description:", name='Description'), - self.getTitledRadioButton("Id_token Subject Type:", name='Id_token', values=['Pairwise','Public'],), - self.getTitledCheckBox("Grant:", name='Grant', values=['Authorizatuin Code', 'Refresh Token', 'UMA Ticket','Client Credential','Password','Implicit']), - self.getTitledCheckBox("Response Types:", name='responseTypes', values=['code', 'token', 'id_token']), - self.getTitledCheckBox("Supress Authorization:", name='Supress', values=['True']), - self.getTitledRadioButton("Application Type:", name='applicationType', values=['web', 'native']), - self.getTitledText("Redirect Uris:", name='redirectUris', height=3), - self.getTitledText("Redirect Regex:", name='redirectregex'), - self.getTitledText("Scopes:", name='scopes', height=3), + self.getTitledText("Client Name", name='displayName',style='green'), + self.getTitledText("Client Secret", name='clientSecret',style='green'), + self.getTitledText("Description", name='Description',style='green'), + self.getTitledRadioButton("Id_token Subject Type:", name='Id_token', values=['Pairwise','Public'],style='green'), + self.getTitledCheckBox("Grant:", name='Grant', values=['Authorizatuin Code', 'Refresh Token', 'UMA Ticket','Client Credential','Password','Implicit'],style='green'), + self.getTitledCheckBox("Response Types:", name='responseTypes', values=['code', 'token', 'id_token'],style='green'), + self.getTitledCheckBox("Supress Authorization:", name='Supress', values=['True'],style='green'), + self.getTitledRadioButton("Application Type:", name='applicationType', values=['web', 'native'],style='green'), + self.getTitledText("Redirect Uris", name='redirectUris', height=3,style='green'), + self.getTitledText("Redirect Regex", name='redirectregex',style='green'), + self.getTitledText("Scopes", name='scopes', height=3,style='green'), + # HSplit([Box(body=VSplit([self.yes_button, self.no_button], align="CENTER", padding=3), style="class:button-bar", height=1)], + # height=D(), + # align = VerticalAlign.BOTTOM, + # ) + ],width=D(), + ) + + self.oauth_tabs['clients']['Tokens'] = HSplit([ + self.getTitledRadioButton("Access Token Type", name='access_token_type', values=['JWT','Reference'],style='green'), + self.getTitledCheckBox("Incliude Claims in id_token", name='id_token_claims', values=['True'],style='green'), + self.getTitledCheckBox("Run introspection script before JWT access token creation", name='Supress_JWT', values=['True'],style='green'), + self.getTitledText("Token binding confirmation method for id_token", name='id_token_binding_confirmation',style='green'), + self.getTitledText("Access token additional audiences", name='access_token_audiences',style='green'), + VSplit([ + Button("+", handler=self.show_again,left_symbol='[',right_symbol=']',width=3) + ]), + self.getTitledText("Access token lifetime", name='access_token_lifetime',style='green'), + self.getTitledText("Refresh token lifetime", name='refresh_token_lifetime',style='green'), + self.getTitledText("Defult max authn age", name='max_authn_age',style='green'), + + ],width=D()) + + self.oauth_tabs['clients']['Logout'] = HSplit([ + + self.getTitledText("Front channel logout URI", name='f_channel_logout_URI',style='green'), + self.getTitledText("Post logout redirect URI", name='p_channel_logout_redirect_URI',style='green'), + self.getTitledText("Back channel logout URI", name='b_channel_logout_URI',style='green'), + self.getTitledCheckBox("Back channel logout session required", name='b_channel_session_required', values=['True'],style='green'), + self.getTitledCheckBox("Front channel logout session required", name='f_channel_session_required', values=['True'],style='green'), + + ],width=D() + ) + + self.oauth_tabs['clients']['Software Info'] = HSplit([ + self.getTitledText("Client URI", name='client_URI',style='green'), + self.getTitledText("Policy URI", name='policy_URI',style='green'), + self.getTitledText("Logo URI", name='logo_URI',style='green'), + self.getTitledText("Term of service URI", name='term_of_service_URI',style='green'), + self.getTitledText("Contacts", name='contacts',style='green'), + VSplit([ + Button("+", handler=self.show_again,left_symbol='[',right_symbol=']',width=3,) + ]), + self.getTitledText("Authorized JS origins", name='authorized_JS_origins',style='green'), + VSplit([ + Button("+", handler=self.show_again,left_symbol='[',right_symbol=']',width=3) + ]), + self.getTitledText("Software id", name='software_id',style='green'), + self.getTitledText("Software version", name='software_version',style='green'), + self.getTitledText("Software statement", name='software_statement',style='green'), + + ],width=D()) + self.oauth_tabs['clients']['CIBA/PAR/UMA'] = HSplit([ + Label(text="CIBA",style='bold'), + self.getTitledRadioButton("Token delivery method", name='applicationType', values=['poll','push', 'ping'],style='green'), + self.getTitledText("Client notification endpoint", name='displayName',style='green'), + self.getTitledCheckBox("Require user code param", name='Supress', values=['True'],style='green'), + + Label(text="PAR",style='bold'), + self.getTitledText("Request lifetime", name='displayName',style='green'), + self.getTitledCheckBox("Request PAR", name='Supress', values=['True'],style='green'), - HSplit([Box(body=VSplit([self.yes_button, self.no_button], align="CENTER", padding=3), style="class:button-bar", height=1)], - height=D(), - align = VerticalAlign.BOTTOM, + Label("UMA",style='bold'), + self.getTitledRadioButton("PRT token type", name='applicationType', values=['JWT', 'Reference'],style='green'), + self.getTitledText("Claims redirect URI", name='displayName',style='green'), + + # self.getButton(text="dropdown1", name='oauth:scopes:dropdown1', jans_help="dropdown1",handler=self.testdropdown), + # self.getButton(text="dropdown2", name='oauth:scopes:dropdown2', jans_help="dropdown2",handler=self.testdropdown), + + Label(text="dropdown1",style='blue'), ## TODO with Jans VerticalNav + Label(text="dropdown2",style='blue'), ## TODO with Jans VerticalNav + Label(text="tabel",style='blue'), ## TODO with Jans VerticalNav + + # JansVerticalNav( + # myparent=self, + # headers=['Client ID', 'Client Name', 'Grant Types', 'Subject Type'], + # preferred_size= [0,0,30,0], + # data=[['1','2','3','4'],['1','2','3','4'],['1','2','3','4'],['1','2','3','4'],], + # on_enter=self.edit_client_dialog, + # on_display=self.data_display_dialog, + # # selection_changed=self.data_selection_changed, + # selectes=0, + # headerColor='green', + # entriesColor='white', + # all_data=[['1','2','3','4'],['1','2','3','4'],['1','2','3','4'],['1','2','3','4'],] + # ) + ] ) - ], - ) - self.oauth_tabs['clients']['Consent Gathering'] = Label(text=str('Consent Gathering')) - self.oauth_tabs['clients']['Post Authentication'] = Label(text=str('Post Authentication')) - self.oauth_tabs['clients']['id_token'] = Label(text=str('id_token')) - self.oauth_tabs['clients']['Password Grant'] = Label(text=str('Password Grant')) - self.oauth_tabs['clients']['CIBA End User Notification'] = Label(text=str('CIBA End User Notification')) - self.oauth_tabs['clients']['OpenId Configuration'] = Label(text=str('OpenId Configuration')) - self.oauth_tabs['clients']['Dynamic Scope'] = Label(text=str('Dynamic Scope')) - self.oauth_tabs['clients']['Spontaneous Scope'] = Label(text=str('Spontaneous Scope')) - self.oauth_tabs['clients']['End Session'] = Label(text=str('End Session')) - self.oauth_tabs['clients']['Client Registation'] = Label(text=str('Client Registation')) - self.oauth_tabs['clients']['Introseption'] = Label(text=str('Introseption')) - self.oauth_tabs['clients']['Update Token'] = Label(text=str('Update Token')) + + self.oauth_tabs['clients']['Encryption/Signing'] = HSplit([ + self.getTitledText("Client JWKS URI", name='displayName',style='green'), + self.getTitledText("Client JWKS", name='displayName',style='green'), + VSplit([ + Label(text="id_token"), + Label(text="a, b, c",style='red'), + ]), + VSplit([ + Label(text="Access token"), + Label(text="a",style='red'), + ]), + VSplit([ + Label(text="Userinfo"), + Label(text="a, b, c",style='red'), + ]), + VSplit([ + Label(text="JARM"), + Label(text="a, b, c",style='red'), + ]), + VSplit([ + Label(text="Request Object"), + Label(text="a, b, c",style='red'), + ]), + + ] + ) + + self.oauth_tabs['clients']['Advanced Client Properties'] = HSplit([ + + self.getTitledCheckBox("Default Prompt=login", name='Supress', values=['True'],style='green'), + VSplit([ + self.getTitledCheckBox("Persist Authorizations", name='Supress', values=['True'],style='green'), + self.getTitledCheckBox("Keep expired?", name='Supress', values=['True'],style='green'), + ]) , + self.getTitledCheckBox("Allow spontaneos scopes", name='Supress', values=['True'],style='green'), + self.getTitledText("spontaneos scopes validation regex", name='displayName',style='green'), + VSplit([ + Label(text="Spontaneous Scopes",style='green'), + Button("view current", handler=self.show_again,left_symbol='',right_symbol='',) + + ]) , + self.getTitledText("Initial Login URI", name='displayName',style='green'), + + VSplit([ + self.getTitledText("Request URIs", name='clientSecret',style='green'), + Button("+", handler=self.show_again,left_symbol='[',right_symbol=']',width=3,) + ]) , + + Label(text="Dropdown 3",style='blue'), + + VSplit([ + self.getTitledText("Allowed ACRs", name='clientSecret',style='green'), + Button("+", handler=self.show_again,left_symbol='[',right_symbol=']',width=3,) + ]) , + self.getTitledText("TLS Subject DN", name='clientSecret',style='green'), + + VSplit([ + self.getTitledCheckBox("Client Experiation Date", name='id_token_claims', values=['True'],style='green'), + Label(text="Pick Date",style='blue'), + ]) , + + + + ],width=D() + ) + + self.oauth_tabs['clients']['Client Scripts'] = HSplit([ + Label(text="Dropdown 4",style='blue'), + Label(text="Dropdown 5",style='blue'), + Label(text="Dropdown 6",style='blue'), + Label(text="Dropdown 7",style='blue'), + Label(text="Dropdown 8",style='blue'), + ] + ) + + + self.oauth_tabs['clients']['Save'] = HSplit([ + Button("Save", handler=self.show_again,left_symbol='(',right_symbol=')',) + + ],width=D() + ) + self.oauth_dialog_nav = list(self.oauth_tabs['clients'].keys())[0] + def client_dialog_nav_selection_changed(self, selection): self.oauth_dialog_nav = selection @@ -282,4 +432,4 @@ def display_scope(self): pass def edit_scope(self, selected,event,size): ## enter - self.edit_scope_dialog() + self.edit_scope_dialog() \ No newline at end of file diff --git a/jans-cli-tui/wui_components/jans_vetrical_nav.py b/jans-cli-tui/wui_components/jans_vetrical_nav.py index 845f384189d..f27d512d32a 100644 --- a/jans-cli-tui/wui_components/jans_vetrical_nav.py +++ b/jans-cli-tui/wui_components/jans_vetrical_nav.py @@ -30,8 +30,8 @@ def __init__(self, myparent, headers, selectes, on_enter, on_display, self.mod_data = data self.all_data=all_data self.view_data() - self.handel_Header_spaces() - self.handel_Data_spaces() + self.handle_Header_spaces() + self.handle_Data_spaces() self.create_window() def view_data(self): @@ -99,7 +99,7 @@ def create_window(self): ], ) - def handel_Header_spaces(self): + def handle_Header_spaces(self): datalen = [] for dataline in range(len(self.mod_data )): line = [] @@ -117,16 +117,16 @@ def handel_Header_spaces(self): for i in dict: self.spaces.append(max(dict[i])) - for i in range(len(self.spaces)): ## handel header collesion (when the headers length is greater that the tallest data index length) + for i in range(len(self.spaces)): ## handle header collesion (when the headers length is greater that the tallest data index length) if self.spaces[i] < len(self.headers[i]): self.spaces[i] = self.spaces[i] + \ (len(self.headers[i]) - self.spaces[i]) - self.spaces[-1] = self.myparent.output.get_size()[1] - sum(self.spaces) + sum(len(s) for s in self.headers) ## handel last head spaces (add space to the end of ter. width to remove the white line) + self.spaces[-1] = self.myparent.output.get_size()[1] - sum(self.spaces) + sum(len(s) for s in self.headers) ## handle last head spaces (add space to the end of ter. width to remove the white line) # -------------------------------------------------------------------------------- # # -------------------------------------------------------------------------------- # - def handel_Data_spaces(self): + def handle_Data_spaces(self): for i in range(len(self.mod_data)): for k in range(len(self.spaces)): if len(self.mod_data[i][k]) != self.spaces[k]: From 8baafb56dd3f3cdadabf8c0fe58b45eab3f233cc Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Wed, 17 Aug 2022 02:17:31 -0700 Subject: [PATCH 018/364] feat: jans-cli implement add handle_long_string and client tabs --- jans-cli-tui/jans-cli-tui.py | 1 + jans-cli-tui/models/oauth.py | 1 - jans-cli-tui/wui_components/jans_vetrical_nav.py | 3 +-- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index 9b7e4e62e6f..9b7d6f461ba 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -247,6 +247,7 @@ def set_keybindings(self): self.bindings.add("c-c")(do_exit) # ----------------------------------------------------------------- # + def handle_long_string (self,text,values,cb): lines = [] if len(text) > 20 : diff --git a/jans-cli-tui/models/oauth.py b/jans-cli-tui/models/oauth.py index 4f49cee2a46..3bbafddc0cc 100644 --- a/jans-cli-tui/models/oauth.py +++ b/jans-cli-tui/models/oauth.py @@ -388,7 +388,6 @@ def update_oauth_scopes(self): data =[] - for d in result: data.append( [ diff --git a/jans-cli-tui/wui_components/jans_vetrical_nav.py b/jans-cli-tui/wui_components/jans_vetrical_nav.py index f27d512d32a..9f8179c134d 100644 --- a/jans-cli-tui/wui_components/jans_vetrical_nav.py +++ b/jans-cli-tui/wui_components/jans_vetrical_nav.py @@ -19,8 +19,7 @@ def __init__(self, myparent, headers, selectes, on_enter, on_display, self.myparent = myparent # ListBox parent class self.headers = headers # ListBox headers self.selectes = selectes # ListBox initial selection - # ListBox Data (Can be renderable ?!!! #TODO ) - self.data = data + self.data = data # ListBox Data (Can be renderable ?!!! #TODO ) self.preferred_size = preferred_size self.headerColor = headerColor self.entriesColor = entriesColor From b4cd00c12e1a0597c0601748c68f1988fa2ac442 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Wed, 17 Aug 2022 12:42:15 +0300 Subject: [PATCH 019/364] fix: jans-cli remove interactive mode from CLI --- jans-cli-tui/cli/config_cli.py | 838 +-------------------------------- 1 file changed, 18 insertions(+), 820 deletions(-) diff --git a/jans-cli-tui/cli/config_cli.py b/jans-cli-tui/cli/config_cli.py index ce5f0c8db97..78d8406fb99 100755 --- a/jans-cli-tui/cli/config_cli.py +++ b/jans-cli-tui/cli/config_cli.py @@ -42,16 +42,6 @@ except ModuleNotFoundError: from pylib import jwt -tabulate_endpoints = { - 'jca.get-config-scripts': ['scriptType', 'name', 'enabled', 'inum'], - 'jca.get-user': ['inum', 'userId', 'mail','sn', 'givenName', 'jansStatus'], - 'jca.get-attributes': ['inum', 'name', 'displayName', 'status', 'dataType', 'claimName'], - 'jca.get-oauth-openid-clients': ['inum', 'displayName', 'clientName', 'applicationType'], - 'jca.get-oauth-scopes': ['dn', 'id', 'scopeType'], - 'scim.get-users': ['id', 'userName', 'displayName', 'active'] -} - -tabular_dataset = {'scim.get-users': 'Resources'} my_op_mode = 'scim' if 'scim' in os.path.basename(sys.argv[0]) else 'jca' plugins = [] @@ -201,16 +191,14 @@ def get_named_tag(tag): client_secret_enc = config['DEFAULT'][secret_enc_key_str] client_secret = encode_decode(client_secret_enc, decode=True) + if 'access_token' in config['DEFAULT']: + access_token = config['DEFAULT']['access_token'] + elif 'access_token_enc' in config['DEFAULT']: + access_token = encode_decode(config['DEFAULT']['access_token_enc'], decode=True) + debug = config['DEFAULT'].get('debug') log_dir = config['DEFAULT'].get('log_dir', log_dir) - else: - config['DEFAULT'] = {'jans_host': 'jans server hostname,e.g, jans.foo.net', - 'jca_client_id': 'your jans config api client id', - 'jca_client_secret': 'client secret for your jans config api client', - 'scim_client_id': 'your jans scim client id', - 'scim_client_secret': 'client secret for your jans scim client'} - def get_bool(val): if str(val).lower() in ('yes', 'true', '1', 'on'): @@ -224,71 +212,6 @@ def write_config(): debug = get_bool(debug) -class Menu(object): - - def __init__(self, name, method='', info={}, path=''): - self.name = name - self.display_name = name - self.method = method - self.info = info - self.path = path - self.children = [] - self.parent = None - self.ignore = False - - def __iter__(self): - self.current_index = 0 - return self - - def __repr__(self): - return self.display_name - self.__print_child(self) - - def tree(self): - print(self.name) - self.__print_child(self) - - def __get_parent_number(self, child): - n = 0 - while True: - if not child.parent: - break - n += 1 - child = child.parent - - return n - - def __print_child(self, menu): - if menu.children: - for child in menu.children: - print(' ' * self.__get_parent_number(child) * 2, child) - self.__print_child(child) - - def add_child(self, node): - assert isinstance(node, Menu) - node.parent = self - self.children.append(node) - - def get_child(self, n): - if len(self.children) > n: - return self.children[n] - - def __next__(self): - if self.current_index < len(self.children): - retVal = self.children[self.current_index] - self.current_index += 1 - return retVal - - else: - raise StopIteration - - def __contains__(self, child): - for child_ in self.children: - if child_.name == child: - return True - return False - - class JCA_CLI: def __init__(self, host, client_id, client_secret, access_token, test_client=False): @@ -296,26 +219,20 @@ def __init__(self, host, client_id, client_secret, access_token, test_client=Fal self.client_id = client_id self.client_secret = client_secret self.use_test_client = test_client - self.getCredintials() + self.getCredentials() self.wrapped = __name__ != "__main__" self.access_token = access_token or config['DEFAULT'].get('access_token') self.jwt_validation_url = 'https://{}/jans-config-api/api/v1/acrs'.format(self.idp_host) self.set_user() self.plugins() - if not self.access_token and config['DEFAULT'].get('access_token_enc'): - self.access_token = encode_decode(config['DEFAULT']['access_token_enc'], decode=True) - if my_op_mode == 'scim': self.host += '/jans-scim/restv1/v2' self.set_logging() self.ssl_settings() - self.make_menu() - self.current_menu = self.menu - self.enums() - def getCredintials(self): + def getCredentials(self): if self.host == '' or self.client_id == '' or self.client_secret == '' : if config_ini_fn.exists(): config.read_string(config_ini_fn.read_text()) @@ -360,15 +277,6 @@ def log_response(self, response): self.cli_logger.debug('requests response headers: %s', str(response.headers)) self.cli_logger.debug('requests response text: %s', str(response.text)) - def enums(self): - self.enum_dict = { - "CustomAttribute": { - "properties.name": { - "f": "get_attrib_list" - } - } - } - def set_user(self): self.auth_username = None self.auth_password = None @@ -490,105 +398,6 @@ def validate_date_time(self, date_str): return False - def make_menu(self): - - menu_groups = [] - - def get_sep_pos(s): - for i, c in enumerate(s): - if c in ('-', '–'): - return i - return -1 - - def get_group_obj(mname): - for grp in menu_groups: - if grp.mname == mname: - return grp - - - for tag in cfg_yml['tags']: - tname = tag['name'].strip() - if tname == 'developers': - continue - n = get_sep_pos(tname) - mname = tname[:n].strip() if n > -1 else tname - grp = get_group_obj(mname) - if not grp: - grp = SimpleNamespace() - grp.tag = None if n > -1 else tname - grp.mname = mname - grp.submenu = [] - menu_groups.append(grp) - - if n > -1: - sname = tname[n+1:].strip() - sub = SimpleNamespace() - sub.tag = tname - sub.mname = sname - grp.submenu.append(sub) - - - def get_methods_of_tag(tag): - methods = [] - if tag: - for path_name in cfg_yml['paths']: - path = cfg_yml['paths'][path_name] - for method_name in path: - method = path[method_name] - if 'tags' in method and tag in method['tags'] and 'operationId' in method: - if method.get('x-cli-plugin') and method['x-cli-plugin'] not in plugins: - continue - method['__method_name__'] = method_name - method['__path_name__'] = path_name - methods.append(method) - - return methods - - menu = Menu('Main Menu') - - - for grp in menu_groups: - methods = get_methods_of_tag(grp.tag) - m = Menu(name=grp.mname) - m.display_name = m.name + ' ˅' - menu.add_child(m) - - for method in methods: - for tag in method['tags']: - menu_name = method.get('summary') or method.get('description') - sm = Menu( - name=menu_name.strip('.'), - method=method['__method_name__'], - info=method, - path=method['__path_name__'], - ) - m.add_child(sm) - - if grp.submenu: - m.display_name = m.name + ' ˅' - for sub in grp.submenu: - methods = get_methods_of_tag(sub.tag) - if not methods: - continue - smenu = Menu(name=sub.mname) - smenu.display_name = smenu.name + ' ˅' - m.add_child(smenu) - - for method in methods: - for tag in method['tags']: - - sub_menu_name = method.get('summary') or method.get('description') - ssm = Menu( - name=sub_menu_name.strip('.'), - method=method['__method_name__'], - info=method, - path=method['__path_name__'], - ) - smenu.add_child(ssm) - - self.menu = menu - - def get_scoped_access_token(self, scope): if not self.wrapped: @@ -1101,17 +910,6 @@ def get_scope_for_endpoint(self, endpoint): return ' '.join(scope) - def tabular_data(self, data, ome): - tab_data = [] - headers = tabulate_endpoints[ome] - for i, entry in enumerate(data): - row_ = [i + 1] - for header in headers: - row_.append(str(entry.get(header, ''))) - tab_data.append(row_) - - print(tabulate(tab_data, ['#']+headers, tablefmt="grid")) - def get_requests(self, endpoint, params={}): if not self.wrapped: @@ -1137,8 +935,8 @@ def get_requests(self, endpoint, params={}): cert=self.mtls_client_cert ) self.log_response(response) - if response.status_code == 404: - print(self.colored_text("Server returned 404", error_color)) + if response.status_code in (404, 401): + print(self.colored_text("Server returned {}".format(response.status_code), error_color)) print(self.colored_text(response.text, error_color)) return None @@ -1149,87 +947,6 @@ def get_requests(self, endpoint, params={}): print("An error ocurred while retreiving data") self.print_exception(e) - def process_get(self, endpoint, return_value=False, parameters=None, noprompt=False, update=False): - clear() - if not return_value: - title = endpoint.name - if endpoint.name != endpoint.info['description'].strip('.'): - title += '\n' + endpoint.info['description'] - - self.print_underlined(title) - - if not parameters and not noprompt: - parameters = self.obtain_parameters(endpoint, single=return_value) - - for param in parameters.copy(): - if not parameters[param]: - del parameters[param] - - if parameters and not return_value: - print("Calling Api with parameters:", parameters) - - data = self.get_requests(endpoint, parameters) - - if return_value: - return data - - selections = ['q', 'x', 'b'] - item_counters = [] - tabulated = False - - if data: - try: - if 'response' in data: - data = data['response'] - except: - pass - - op_mode_endpoint = my_op_mode + '.' + endpoint.info['operationId'] - import copy - if op_mode_endpoint in tabulate_endpoints: - try: - data_ext = copy.deepcopy(data) - if endpoint.info['operationId'] == 'get-user': - for entry in data_ext: - if entry.get('customAttributes'): - for attrib in entry['customAttributes']: - if attrib['name'] == 'mail': - entry['mail'] = ', '.join(attrib['values']) - elif attrib['name'] in tabulate_endpoints[op_mode_endpoint]: - entry[attrib['name']] = attrib['values'][0] - - tab_data = data_ext - if op_mode_endpoint in tabular_dataset: - tab_data = data_ext[tabular_dataset[op_mode_endpoint]] - self.tabular_data(tab_data, op_mode_endpoint) - item_counters = [str(i + 1) for i in range(len(data))] - tabulated = True - except: - self.pretty_print(data) - else: - self.pretty_print(data) - - if update: - return item_counters, data - - selections += item_counters - while True: - selection = self.get_input(selections) - if selection == 'b': - self.display_menu(endpoint.parent) - break - elif selection == 'w': - fn = input('File name: ') - try: - with open(fn, 'w') as w: - json.dump(data, w, indent=2) - print("Output was written to", fn) - except Exception as e: - print("An error ocurred while saving data") - self.print_exception(e) - elif selection in item_counters: - self.pretty_print(data[int(selection) - 1]) - def get_schema_from_reference(self, ref): schema_path_list = ref.strip('/#').split('/') schema = cfg_yml[schema_path_list[0]] @@ -1272,140 +989,6 @@ def get_schema_from_reference(self, ref): return schema_ - def get_schema_for_endpoint(self, endpoint): - schema_ = {} - for content_type in endpoint.info.get('requestBody', {}).get('content', {}): - if 'schema' in endpoint.info['requestBody']['content'][content_type]: - schema = endpoint.info['requestBody']['content'][content_type]['schema'] - break - else: - return schema_ - - schema_ = schema.copy() - - if schema_.get('type') == 'array': - schma_ref = schema_.get('items', {}).pop('$ref') - else: - schma_ref = schema_.pop('$ref') - - if schma_ref: - schema_ref_ = self.get_schema_from_reference(schma_ref) - schema_.update(schema_ref_) - - return schema_ - - - def get_attrib_list(self): - for parent in self.menu: - for children in parent: - if children.info['operationId'] == 'get-attributes': - attributes = self.process_get(children, return_value=True, parameters={'limit': 1000} ) - attrib_names = [] - for a in attributes: - attrib_names.append(a['name']) - attrib_names.sort() - return attrib_names - - def get_enum(self, schema): - if schema['__schema_name__'] in self.enum_dict: - enum_obj = schema - - for path in self.enum_dict[schema['__schema_name__']].copy(): - for p in path.split('.'): - enum_obj = enum_obj[p] - - if not 'enum' in self.enum_dict[schema['__schema_name__']][path]: - self.enum_dict[schema['__schema_name__']][path]['enum'] = getattr(self, self.enum_dict[schema['__schema_name__']][path]['f'])() - - enum_obj['enum'] = self.enum_dict[schema['__schema_name__']][path]['enum'] - - - def get_input_for_schema_(self, schema, data={}, spacing=0, getitem=None, required_only=False): - - self.get_enum(schema) - - for prop in schema['properties']: - item = schema['properties'][prop] - if getitem and prop != getitem['__name__'] or prop in ('dn', 'inum'): - continue - - if (required_only and schema.get('required')) and not prop in schema.get('required'): - continue - - - if item['type'] == 'object' and 'properties' in item: - - print() - data_obj = {} - print("Data for object {}. {}".format(prop, item.get('description', ''))) - result = self.get_input_for_schema_(item, data_obj) - data[prop] = result - #model_name_str = item.get('__schema_name__') or item.get('title') or item.get('description') - #model_name = self.get_name_from_string(model_name_str) - - #if initialised and getattr(model, prop_): - # sub_model = getattr(model, prop_) - # self.get_input_for_schema_(item, sub_model, spacing=3, initialised=initialised) - #elif isinstance(model, type) and hasattr(swagger_client.models, model_name): - # sub_model_class = getattr(swagger_client.models, model_name) - # result = self.get_input_for_schema_(item, sub_model_class, spacing=3, initialised=initialised) - # setattr(model, prop_, result) - #elif hasattr(swagger_client.models, model.swagger_types[prop_]): - # sub_model = getattr(swagger_client.models, model.swagger_types[prop_]) - # result = self.get_input_for_schema_(item, sub_model, spacing=3, initialised=initialised) - # setattr(model, prop_, result) - #else: - # sub_model = getattr(model, prop_) - # self.get_input_for_schema_(item, sub_model, spacing=3, initialised=initialised) - # # print(self.colored_text("Fix me: can't find model", error_color)) - - elif item['type'] == 'array' and '__schema_name__' in item: - - sub_data_list = [] - sub_data_list_title_text = item.get('title') or item.get('description') or prop - sub_data_list_selection = 'y' - print(sub_data_list_title_text) - while sub_data_list_selection == 'y': - sub_data = {} - self.get_input_for_schema_(item, sub_data, spacing=spacing + 3) - sub_data_list.append(sub_data) - sub_data_list_selection = self.get_input( - text="Add another {}?".format(sub_data_list_title_text), values=['y', 'n']) - data[prop] = sub_data_list - - else: - - default = data.get(prop) or item.get('default') - values_ = item.get('enum',[]) - - if isinstance(default, property): - default = None - enforce = True if item['type'] == 'boolean' else False - - if prop in schema.get('required', []): - enforce = True - if not values_ and item['type'] == 'array' and 'enum' in item['items']: - values_ = item['items']['enum'] - if item['type'] == 'object' and not default: - default = {} - - val = self.get_input( - values=values_, - text=prop, - default=default, - itype=item['type'], - help_text=item.get('description'), - sitype=item.get('items', {}).get('type'), - enforce=enforce, - example=item.get('example'), - spacing=spacing, - iformat=item.get('format') - ) - - data[prop] = val - - return data - def post_requests(self, endpoint, data): url = 'https://{}{}'.format(self.host, endpoint.path) @@ -1428,70 +1011,6 @@ def post_requests(self, endpoint, data): except: self.print_exception(response.text) - def process_post(self, endpoint): - schema = self.get_schema_for_endpoint(endpoint) - - if schema: - - title = schema.get('description') or schema['title'] - data_dict = {} - - if my_op_mode == 'scim': - if endpoint.path == '/jans-scim/restv1/v2/Groups': - schema['properties']['schemas']['default'] = ['urn:ietf:params:scim:schemas:core:2.0:Group'] - elif endpoint.path == '/jans-scim/restv1/v2/Users': - schema['properties']['schemas']['default'] = ['urn:ietf:params:scim:schemas:core:2.0:User'] - if endpoint.info['operationId'] == 'create-user': - schema['required'] = ['userName', 'name', 'displayName', 'emails', 'password'] - - data = self.get_input_for_schema_(schema, data_dict, required_only=True) - - optional_fields = [] - required_fields = schema.get('required', []) + ['dn', 'inum'] - for field in schema['properties']: - if not field in required_fields: - optional_fields.append(field) - - if optional_fields: - fill_optional = self.get_input(values=['y', 'n'], text='Populate optional fields?') - fields_numbers = [] - if fill_optional == 'y': - print("Optional Fields:") - for i, field in enumerate(optional_fields): - print(i + 1, field) - fields_numbers.append(str(i + 1)) - - while True: - optional_selection = self.get_input(values=['q', 'x', 'c'] + fields_numbers, - help_text="c: continue, #: populate field") - if optional_selection == 'c': - break - if optional_selection in fields_numbers: - item_name = optional_fields[int(optional_selection) - 1] - schema_item = schema['properties'][item_name].copy() - schema_item['__name__'] = item_name - item_data = self.get_input_for_schema_(schema, getitem=schema_item) - data[item_name] = item_data[item_name] - - print("Obtained Data:") - self.pretty_print(data) - - selection = self.get_input(values=['q', 'x', 'b', 'y', 'n'], text='Continue?') - - else: - selection = 'y' - model = None - - if selection == 'y': - print("Please wait while posting data ...\n") - response = self.post_requests(endpoint, data) - if response: - self.pretty_print(response) - - selection = self.get_input(values=['q', 'x', 'b']) - if selection in ('b', 'n'): - self.display_menu(endpoint.parent) - def delete_requests(self, endpoint, url_param_dict): security = self.get_scope_for_endpoint(endpoint) @@ -1510,33 +1029,6 @@ def delete_requests(self, endpoint, url_param_dict): return response.text.strip() - def process_delete(self, endpoint): - url_param = self.get_endpiont_url_param(endpoint) - if url_param: - url_param_val = self.get_input(text=url_param['name'], help_text='Entry to be deleted') - else: - url_param_val = '' - selection = self.get_input(text="Are you sure want to delete {} ?".format(url_param_val), - values=['b', 'y', 'n', 'q', 'x']) - if selection in ('b', 'n'): - self.display_menu(endpoint.parent) - elif selection == 'y': - print("Please wait while deleting {} ...\n".format(url_param_val)) - - url_param_dict = {url_param['name']: url_param_val} - - response = self.delete_requests(endpoint, url_param_dict) - - if response is None: - print(self.colored_text("\nEntry {} was deleted successfully\n".format(url_param_val), success_color)) - else: - print(self.colored_text("An error ocurred while deleting entry:", error_color)) - print(self.colored_text(response, error_color)) - - selection = self.get_input(['b', 'q', 'x']) - if selection == 'b': - self.display_menu(endpoint.parent) - def patch_requests(self, endpoint, url_param_dict, data): url = 'https://{}{}'.format(self.host, endpoint.path.format(**url_param_dict)) security = self.get_scope_for_endpoint(endpoint) @@ -1558,73 +1050,6 @@ def patch_requests(self, endpoint, url_param_dict, data): self.print_exception(response.text) - def process_patch(self, endpoint): - - schema = self.get_schema_for_endpoint(endpoint) - - if 'PatchOperation' in cfg_yml['components']['schemas']: - schema = cfg_yml['components']['schemas']['PatchOperation'].copy() - schema['__schema_name__'] = 'PatchOperation' - else: - schema = cfg_yml['components']['schemas']['PatchRequest'].copy() - schema['__schema_name__'] = 'PatchRequest' - - - for item in schema['properties']: - print("d-checking", item) - if not 'type' in schema['properties'][item] or schema['properties'][item]['type']=='object': - schema['properties'][item]['type'] = 'string' - - url_param_dict = {} - url_param_val = None - url_param = self.get_endpiont_url_param(endpoint) - if 'name' in url_param: - url_param_val = self.get_input(text=url_param['name'], help_text='Entry to be patched') - url_param_dict = {url_param['name']: url_param_val} - - body = [] - - while True: - data = self.get_input_for_schema_(schema) - #guessed_val = self.guess_bool(data.value) - #if not guessed_val is None: - # data.value = guessed_val - if my_op_mode != 'scim' and not data['path'].startswith('/'): - data['path'] = '/' + data['path'] - - if my_op_mode == 'scim': - data['path'] = data['path'].replace('/', '.') - - body.append(data) - selection = self.get_input(text='Another patch operation?', values=['y', 'n']) - if selection == 'n': - break - - if endpoint.info['operationId'] == 'patch-oauth-uma-resources-by-id': - for patch_item in body: - if patch_item['path'] == '/clients': - patch_item['value'] = patch_item['value'].split('_,') - - self.pretty_print(body) - - selection = self.get_input(values=['y', 'n'], text='Continue?') - - if selection == 'y': - - if my_op_mode == 'scim': - body = {'schemas': ['urn:ietf:params:scim:api:messages:2.0:PatchOp'], 'Operations': body} - - self.patch_requests(endpoint, url_param_dict, body) - - selection = self.get_input(['b']) - if selection == 'b': - self.display_menu(endpoint.parent) - - def get_mime_for_endpoint(self, endpoint, req='requestBody'): - for key in endpoint.info[req]['content']: - return key - - def put_requests(self, endpoint, data): security = self.get_scope_for_endpoint(endpoint) @@ -1648,219 +1073,6 @@ def put_requests(self, endpoint, data): return result - def process_put(self, endpoint): - - schema = self.get_schema_for_endpoint(endpoint) - - cur_data = None - go_back = False - - if endpoint.info.get('x-cli-getdata') != '_file': - if 'x-cli-getdata' in endpoint.info and endpoint.info['x-cli-getdata'] != None: - for m in endpoint.parent: - if m.info['operationId'] == endpoint.info['x-cli-getdata']: - while True: - try: - print("cur_data-1") - cur_data = self.process_get(m, return_value=True) - break - except ValueError as e: - retry = self.get_input(values=['y', 'n'], text='Retry?') - if retry == 'n': - self.display_menu(endpoint.parent) - break - get_endpoint = m - break - - else: - for mi in endpoint.parent : - if mi.method == 'get' and not mi.path.endswith('}'): - cur_data = self.process_get(mi, noprompt=True, update=True) - values = ['b', 'q', 'x'] + cur_data[0] - item_number = self.get_input(text="Enter item # to update", values=values) - - cur_data = cur_data[1][int(item_number) -1] - """ - for m in endpoint.parent: - if m.method == 'get' and m.path.endswith('}'): - while True: - while True: - try: - - key_name_desc = self.get_endpiont_url_param(m) - if key_name_desc and 'name' in key_name_desc: - key_name = key_name_desc['name'] - print("P-X", m) - cur_data = self.process_get(m, return_value=True) - break - except ValueError as e: - retry = self.get_input(values=['y', 'n'], text='Retry?') - if retry == 'n': - self.display_menu(endpoint.parent) - break - - if cur_data is not None: - break - - get_endpoint = m - break - """ - if not cur_data: - for m in endpoint.parent: - if m.method == 'get' and not m.path.endswith('}'): - cur_data = self.process_get(m, return_value=True) - get_endpoint = m - - end_point_param = self.get_endpiont_url_param(endpoint) - - if endpoint.info.get('x-cli-getdata') == '_file': - - # TODO: To be implemented - schema_desc = schema.get('description') or schema['__schema_name__'] - text = 'Enter filename to load data for «{}»: '.format(schema_desc) - data_fn = input(self.colored_text(text, 244)) - if data_fn == 'b': - go_back = True - elif data_fn == 'q': - sys.exit() - else: - data_org = self.get_json_from_file(data_fn) - - data = {} - for k in data_org: - if k in cur_model.attribute_map: - mapped_key = cur_model.attribute_map[k] - data[mapped_key] = data_org[k] - else: - data[k] = data_org[k] - - print("Please wait while posting data ...\n") - - response = self.put_requests(endpoint, cur_data) - - if response: - self.pretty_print(response) - - selection = self.get_input(values=['q', 'x', 'b']) - if selection == 'b': - self.display_menu(endpoint.parent) - - else: - - end_point_param_val = None - if end_point_param and end_point_param['name'] in cur_data: - end_point_param_val = cur_data[end_point_param['name']] - - schema = self.get_schema_for_endpoint(endpoint) - if schema['properties'].get('keys', {}).get('properties'): - schema = schema['properties']['keys'] - - - attr_name_list = list(schema['properties'].keys()) - if 'dn' in attr_name_list: - attr_name_list.remove('dn') - - attr_name_list.sort() - item_numbers = [] - - def print_fields(): - print("Fields:") - for i, attr_name in enumerate(attr_name_list): - print(str(i + 1).rjust(2), attr_name) - item_numbers.append(str(i + 1)) - - print_fields() - changed_items = [] - selection_list = ['q', 'x', 'b', 'v', 's', 'l'] + item_numbers - help_text = 'q: quit, v: view, s: save, l: list fields #: update field' - - while True: - selection = self.get_input(values=selection_list, help_text=help_text) - if selection == 'v': - self.pretty_print(cur_data) - elif selection == 'l': - print_fields() - elif selection in item_numbers: - item = attr_name_list[int(selection) - 1] - - schema_item = schema['properties'][item] - schema_item['__name__'] = item - self.get_input_for_schema_(schema, data=cur_data, getitem=schema_item) - changed_items.append(item) - - if selection == 'b': - self.display_menu(endpoint.parent) - break - elif selection == 's': - print('Changes:') - for ci in changed_items: - str_val = str(cur_data[ci]) - print(self.colored_text(ci, bold_color) + ':', self.colored_text(str_val, success_color)) - - selection = self.get_input(values=['y', 'n'], text='Continue?') - - if selection == 'y': - print("Please wait while posting data ...\n") - put_pname = self.get_url_param(endpoint.path) - - response = self.put_requests(endpoint, cur_data) - - if response: - self.pretty_print(response) - go_back = True - break - - if go_back: - selection = self.get_input(values=['q', 'x', 'b']) - if selection == 'b': - self.display_menu(endpoint.parent) - else: - self.get_input_for_schema_(schema, data=cur_data) - - def display_menu(self, menu): - clear() - self.current_menu = menu - - name_list = [menu.name] - par = menu - while True: - par = par.parent - if not par: - break - name_list.insert(0, par.name) - - if len(name_list) > 1: - del name_list[0] - - self.print_underlined(': '.join(name_list)) - - selection_values = ['q', 'x', 'b'] - - menu_numbering = {} - - c = 0 - for i, item in enumerate(menu): - if item.info.get('x-cli-ignore') or (item.parent.name == 'Main Menu' and not item.children): - continue - - print(c + 1, item) - selection_values.append(str(c + 1)) - menu_numbering[c + 1] = i - c += 1 - - selection = self.get_input(selection_values) - - if selection == 'b' and not menu.parent: - print("Quiting...") - sys.exit() - elif selection == 'b': - self.display_menu(menu.parent) - elif int(selection) in menu_numbering and menu.get_child(menu_numbering[int(selection)]).children: - self.display_menu(menu.get_child(menu_numbering[int(selection)])) - else: - m = menu.get_child(menu_numbering[int(selection)]) - getattr(self, 'process_' + m.method)(m) - def parse_command_args(self, args): args_dict = {} @@ -2073,9 +1285,6 @@ def process_command_by_id(self, operation_id, url_suffix, endpoint_args, data_fn if path.get('__urlsuffix__') and not path['__urlsuffix__'] in suffix_param: self.exit_with_error("This operation requires a value for url-suffix {}".format(path['__urlsuffix__'])) - endpoint = Menu('', info=path) - schema = self.get_schema_for_endpoint(endpoint) - if not data: op_path = self.get_path_by_id(operation_id) if op_path['__method__'] == 'patch' and not data_fn: @@ -2100,9 +1309,6 @@ def process_command_by_id(self, operation_id, url_suffix, endpoint_args, data_fn else: data = [{'op': pop, 'path': '/'+ pdata.lstrip('/')}] - if (schema and not data_fn) and not data: - self.exit_with_error("Please provide schema with --data argument") - caller_function = getattr(self, 'process_command_' + path['__method__']) return caller_function(path, suffix_param, endpoint_params, data_fn, data=data) @@ -2137,29 +1343,22 @@ def get_sample_schema(self, ref): print(json.dumps(sample_schema, indent=2)) - def runApp(self): - clear() - self.display_menu(self.menu) def main(): - + error_log_file = os.path.join(log_dir, 'cli_eorror.log') cli_object = JCA_CLI(host, client_id, client_secret, access_token, test_client) if not os.path.exists(log_dir): os.makedirs(log_dir) - try: + if 1: + #try: if not access_token: cli_object.check_connection() - if not (args.operation_id or args.info or args.schema): - # reset previous color - print('\033[0m', end='') - cli_object.runApp() else: - print() if args.info: cli_object.help_for(args.info) elif args.schema: @@ -2167,13 +1366,12 @@ def main(): elif args.operation_id: cli_object.process_command_by_id(args.operation_id, args.url_suffix, args.endpoint_args, args.data) print() - except Exception as e: - if os.environ.get('errstdout'): - print(traceback.print_exc()) - print(u"\u001b[38;5;{}mAn Unhandled error raised: {}\u001b[0m".format(error_color, e)) - with open(error_log_file, 'a') as w: - traceback.print_exc(file=w) - print("Error is logged to {}".format(error_log_file)) + + #except Exception as e: + # print(u"\u001b[38;5;{}mAn Unhandled error raised: {}\u001b[0m".format(error_color, e)) + # with open(error_log_file, 'a') as w: + # traceback.print_exc(file=w) + # print("Error is logged to {}".format(error_log_file)) if __name__ == "__main__": From 2c2408e42432713504eb060f6b60aa54879fe1ef Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Wed, 17 Aug 2022 13:51:36 +0300 Subject: [PATCH 020/364] feat: jans-cli add nonfunctional prev next buttons --- jans-cli-tui/models/oauth.py | 14 +++++++++++--- jans-cli-tui/wui_components/jans_vetrical_nav.py | 2 +- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/jans-cli-tui/models/oauth.py b/jans-cli-tui/models/oauth.py index 3bbafddc0cc..7a8cc3ca9a3 100644 --- a/jans-cli-tui/models/oauth.py +++ b/jans-cli-tui/models/oauth.py @@ -14,6 +14,7 @@ HSplit, VSplit, VerticalAlign, + HorizontalAlign, DynamicContainer, FloatContainer, Window @@ -382,7 +383,7 @@ def oauth_get_clients(self): t = threading.Thread(target=self.oauth_update_clients, daemon=True) t.start() - def update_oauth_scopes(self): + def update_oauth_scopes(self, start_index=0): try : result = self.cli_object.process_command_by_id('get-oauth-scopes', '', 'limit:10', {}) @@ -411,9 +412,16 @@ def update_oauth_scopes(self): all_data=result ) + buttons = [] + if start_index > 0: + buttons.append(Button("Prev")) + if len(result) >= 10: + buttons.append(Button("Next")) + self.layout.focus(clients) # clients.focuse..!? TODO >> DONE self.oauth_data_container['scopes'] = HSplit([ - clients + clients, + VSplit(buttons, padding=5, align=HorizontalAlign.CENTER) ]) get_app().invalidate() @@ -431,4 +439,4 @@ def display_scope(self): pass def edit_scope(self, selected,event,size): ## enter - self.edit_scope_dialog() \ No newline at end of file + self.edit_scope_dialog() diff --git a/jans-cli-tui/wui_components/jans_vetrical_nav.py b/jans-cli-tui/wui_components/jans_vetrical_nav.py index 9f8179c134d..51e95f7b73d 100644 --- a/jans-cli-tui/wui_components/jans_vetrical_nav.py +++ b/jans-cli-tui/wui_components/jans_vetrical_nav.py @@ -90,7 +90,7 @@ def create_window(self): cursorline=True, right_margins=[ScrollbarMargin(display_arrows=True), ], ), - Window(height=10), + Window(height=2), ] ), From 1fb947bd7f385198c6fef9e7a79f2c4dc7fb6320 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Wed, 17 Aug 2022 09:02:34 -0700 Subject: [PATCH 021/364] feat: jans-cli populate data to clients tabs --- jans-cli-tui/checkbox.txt | 30 ++ jans-cli-tui/data_debug.txt | 108 +++++++ jans-cli-tui/hopa.txt | 1 + jans-cli-tui/jans-cli-tui.py | 80 +++--- jans-cli-tui/models/oauth.py | 232 +-------------- jans-cli-tui/mydisct_debug.txt | 1 + jans-cli-tui/radio.txt | 10 + .../wui_components/edit_client_dialog.py | 268 ++++++++++++++++-- .../wui_components/jans_vetrical_nav.py | 2 +- 9 files changed, 448 insertions(+), 284 deletions(-) create mode 100644 jans-cli-tui/checkbox.txt create mode 100644 jans-cli-tui/data_debug.txt create mode 100644 jans-cli-tui/hopa.txt create mode 100644 jans-cli-tui/mydisct_debug.txt create mode 100644 jans-cli-tui/radio.txt diff --git a/jans-cli-tui/checkbox.txt b/jans-cli-tui/checkbox.txt new file mode 100644 index 00000000000..878cadd8163 --- /dev/null +++ b/jans-cli-tui/checkbox.txt @@ -0,0 +1,30 @@ +Name : disabled , value = False +********** +Name : grantTypes , value = +********** +Name : responseTypes , value = +********** +Name : dynamicRegistrationPersistClientAuthorizations , value = +********** +Name : includeClaimsInIdToken , value = False +********** +Name : runIntrospectionScriptBeforeAccessTokenAsJwtCreationAndIncludeClaims , value = +********** +Name : backchannelLogoutSessionRequired , value = +********** +Name : frontChannelLogoutSessionRequired , value = False +********** +Name : backchannelUserCodeParameterSupported , value = +********** +Name : sessionIdRequestParameterEnabled , value = +********** +Name : defaultPromptLogin , value = +********** +Name : persistClientAuthorizations , value = True +********** +Name : Supress , value = +********** +Name : allowSpontaneousScopes , value = +********** +Name : id_token_claims , value = +********** diff --git a/jans-cli-tui/data_debug.txt b/jans-cli-tui/data_debug.txt new file mode 100644 index 00000000000..51e44947a25 --- /dev/null +++ b/jans-cli-tui/data_debug.txt @@ -0,0 +1,108 @@ + + + +{'dn': 'inum=1800.6c5faab4-dc28-4d46-94f4-df320f7c9780, +ou=clients,o=jans', + 'deletable': False, + 'clientSecret': 'oVSD1QqiLWRW', + 'frontChannelLogoutSessionRequired': False, + 'redirectUris': ['https://c1.gluu.org/admin-ui', 'http://localhost:4100'], + 'responseTypes': ['code'], + 'grantTypes': ['authorization_code', 'refresh_token', 'client_credentials'], + 'applicationType': 'web', + 'clientName': {'values': {'': 'Jans Config Api Client'}, + 'languageTags': [''], + 'value': 'Jans Config Api Client'}, + 'logoUri': {}, + 'clientUri': {}, + 'policyUri': {}, + 'tosUri': {}, + 'subjectType': 'pairwise', + 'idTokenSignedResponseAlg': 'RS256', + 'tokenEndpointAuthMethod': 'client_secret_basic', + 'requireAuthTime': False, + 'scopes': ['inum=C4F7,ou=scopes,o=jans', + 'inum=1200.1F13DB,ou=scopes,o=jans', + 'inum=1200.E0B4C8,ou=scopes,o=jans', + 'inum=1800.590346,ou=scopes,o=jans', + 'inum=1800.507006,ou=scopes,o=jans', + 'inum=1800.7866C7,ou=scopes,o=jans', + 'inum=1800.A1D5CD,ou=scopes,o=jans', + 'inum=1800.0714D9,ou=scopes,o=jans', + 'inum=1800.32C574,ou=scopes,o=jans', + 'inum=1800.C1E748,ou=scopes,o=jans', + 'inum=1800.19E881,ou=scopes,o=jans', + 'inum=1800.913FF3,ou=scopes,o=jans', + 'inum=1800.327D46,ou=scopes,o=jans', + 'inum=1800.99C0D3,ou=scopes,o=jans', + 'inum=1800.BBD836,ou=scopes,o=jans', + 'inum=1800.F8AAB3,ou=scopes,o=jans', + 'inum=1800.E03CFE,ou=scopes,o=jans', + 'inum=1800.7DC04D,ou=scopes,o=jans', + 'inum=1800.1DA5BA,ou=scopes,o=jans', + 'inum=1800.926A52,ou=scopes,o=jans', + 'inum=1800.FF17D8,ou=scopes,o=jans', + 'inum=1800.A650F6,ou=scopes,o=jans', + 'inum=1800.580BA9,ou=scopes,o=jans', + 'inum=1800.DC882D,ou=scopes,o=jans', + 'inum=1800.F97827,ou=scopes,o=jans', + 'inum=1800.8FB9E4,ou=scopes,o=jans', + 'inum=1800.0EE56D,ou=scopes,o=jans', + 'inum=1800.FD8734,ou=scopes,o=jans', + 'inum=1800.73E5B5,ou=scopes,o=jans', + 'inum=1800.0DE56F,ou=scopes,o=jans', + 'inum=1800.F0C44A,ou=scopes,o=jans', + 'inum=1800.70AA30,ou=scopes,o=jans', + 'inum=1800.632780,ou=scopes,o=jans', + 'inum=1800.2646DC,ou=scopes,o=jans', + 'inum=1800.54387F,ou=scopes,o=jans', + 'inum=1800.16B85E,ou=scopes,o=jans', + 'inum=1800.565A62,ou=scopes,o=jans', + 'inum=1800.065E1A,ou=scopes,o=jans', + 'inum=1800.4E54A5,ou=scopes,o=jans', + 'inum=1800.50BBFE,ou=scopes,o=jans', + 'inum=1800.6A2BD2,ou=scopes,o=jans', + 'inum=1800.B9BE21,ou=scopes,o=jans', + 'inum=1800.A8FD82,ou=scopes,o=jans', + 'inum=1800.BDA282,ou=scopes,o=jans', + 'inum=1800.0B7536,ou=scopes,o=jans', + 'inum=1800.8100D9,ou=scopes,o=jans', + 'inum=1800.987C52,ou=scopes,o=jans', + 'inum=1800.DB17D4,ou=scopes,o=jans', + 'inum=1800.AA15F1,ou=scopes,o=jans', + 'inum=1800.5B28B1,ou=scopes,o=jans', + 'inum=1800.53B808,ou=scopes,o=jans', + 'inum=1800.573EBC,ou=scopes,o=jans', + 'inum=2762b464-0b67-4071-8072-91cb371895a6, + ou=scopes,o=jans', + 'inum=378ac51e-6746-4b2c-a9dc-ca3261f998e1, + ou=scopes,o=jans', + 'inum=478e15ec-1871-4314-a909-3d2c84bd09f9, + ou=scopes,o=jans'], + 'trustedClient': False, + 'persistClientAuthorizations': True, + 'includeClaimsInIdToken': False, + 'customAttributes': [{'name': 'displayName', + 'multiValued': False, + 'values': ['Jans Config Api Client'], + 'displayValue': 'Jans Config Api Client', + 'value': 'Jans Config Api Client'}], + 'customObjectClasses': ['top'], + 'rptAsJwt': False, + 'accessTokenAsJwt': False, + 'accessTokenSigningAlg': 'RS256', + 'disabled': False, + 'attributes': {'runIntrospectionScriptBeforeAccessTokenAsJwtCreationAndIncludeClaims': False, + 'keepClientAuthorizationAfterExpiration': False, + 'allowSpontaneousScopes': False, + 'backchannelLogoutSessionRequired': False, + 'parLifetime': 600, + 'requirePar': False, + 'jansDefaultPromptLogin': False}, + 'displayName': 'Jans Config Api Client', + 'authenticationMethod': 'client_secret_basic', + 'tokenBindingSupported': False, + 'baseDn': 'inum=1800.6c5faab4-dc28-4d46-94f4-df320f7c9780, + ou=clients,o=jans', + 'inum': '1800.6c5faab4-dc28-4d46-94f4-df320f7c9780' + } diff --git a/jans-cli-tui/hopa.txt b/jans-cli-tui/hopa.txt new file mode 100644 index 00000000000..22ff299c575 --- /dev/null +++ b/jans-cli-tui/hopa.txt @@ -0,0 +1 @@ +{'dn': 'inum=1800.6c5faab4-dc28-4d46-94f4-df320f7c9780,ou=clients,o=jans', 'deletable': False, 'clientSecret': 'oVSD1QqiLWRW', 'frontChannelLogoutSessionRequired': False, 'redirectUris': ['https://c1.gluu.org/admin-ui', 'http://localhost:4100'], 'responseTypes': ['code'], 'grantTypes': ['authorization_code', 'refresh_token', 'client_credentials'], 'applicationType': 'web', 'clientName': {'values': {'': 'Jans Config Api Client'}, 'languageTags': [''], 'value': 'Jans Config Api Client'}, 'logoUri': {}, 'clientUri': {}, 'policyUri': {}, 'tosUri': {}, 'subjectType': 'pairwise', 'idTokenSignedResponseAlg': 'RS256', 'tokenEndpointAuthMethod': 'client_secret_basic', 'requireAuthTime': False, 'scopes': ['inum=C4F7,ou=scopes,o=jans', 'inum=1200.1F13DB,ou=scopes,o=jans', 'inum=1200.E0B4C8,ou=scopes,o=jans', 'inum=1800.590346,ou=scopes,o=jans', 'inum=1800.507006,ou=scopes,o=jans', 'inum=1800.7866C7,ou=scopes,o=jans', 'inum=1800.A1D5CD,ou=scopes,o=jans', 'inum=1800.0714D9,ou=scopes,o=jans', 'inum=1800.32C574,ou=scopes,o=jans', 'inum=1800.C1E748,ou=scopes,o=jans', 'inum=1800.19E881,ou=scopes,o=jans', 'inum=1800.913FF3,ou=scopes,o=jans', 'inum=1800.327D46,ou=scopes,o=jans', 'inum=1800.99C0D3,ou=scopes,o=jans', 'inum=1800.BBD836,ou=scopes,o=jans', 'inum=1800.F8AAB3,ou=scopes,o=jans', 'inum=1800.E03CFE,ou=scopes,o=jans', 'inum=1800.7DC04D,ou=scopes,o=jans', 'inum=1800.1DA5BA,ou=scopes,o=jans', 'inum=1800.926A52,ou=scopes,o=jans', 'inum=1800.FF17D8,ou=scopes,o=jans', 'inum=1800.A650F6,ou=scopes,o=jans', 'inum=1800.580BA9,ou=scopes,o=jans', 'inum=1800.DC882D,ou=scopes,o=jans', 'inum=1800.F97827,ou=scopes,o=jans', 'inum=1800.8FB9E4,ou=scopes,o=jans', 'inum=1800.0EE56D,ou=scopes,o=jans', 'inum=1800.FD8734,ou=scopes,o=jans', 'inum=1800.73E5B5,ou=scopes,o=jans', 'inum=1800.0DE56F,ou=scopes,o=jans', 'inum=1800.F0C44A,ou=scopes,o=jans', 'inum=1800.70AA30,ou=scopes,o=jans', 'inum=1800.632780,ou=scopes,o=jans', 'inum=1800.2646DC,ou=scopes,o=jans', 'inum=1800.54387F,ou=scopes,o=jans', 'inum=1800.16B85E,ou=scopes,o=jans', 'inum=1800.565A62,ou=scopes,o=jans', 'inum=1800.065E1A,ou=scopes,o=jans', 'inum=1800.4E54A5,ou=scopes,o=jans', 'inum=1800.50BBFE,ou=scopes,o=jans', 'inum=1800.6A2BD2,ou=scopes,o=jans', 'inum=1800.B9BE21,ou=scopes,o=jans', 'inum=1800.A8FD82,ou=scopes,o=jans', 'inum=1800.BDA282,ou=scopes,o=jans', 'inum=1800.0B7536,ou=scopes,o=jans', 'inum=1800.8100D9,ou=scopes,o=jans', 'inum=1800.987C52,ou=scopes,o=jans', 'inum=1800.DB17D4,ou=scopes,o=jans', 'inum=1800.AA15F1,ou=scopes,o=jans', 'inum=1800.5B28B1,ou=scopes,o=jans', 'inum=1800.53B808,ou=scopes,o=jans', 'inum=1800.573EBC,ou=scopes,o=jans', 'inum=2762b464-0b67-4071-8072-91cb371895a6,ou=scopes,o=jans', 'inum=378ac51e-6746-4b2c-a9dc-ca3261f998e1,ou=scopes,o=jans', 'inum=478e15ec-1871-4314-a909-3d2c84bd09f9,ou=scopes,o=jans'], 'trustedClient': False, 'persistClientAuthorizations': True, 'includeClaimsInIdToken': False, 'customAttributes': [{'name': 'displayName', 'multiValued': False, 'values': ['Jans Config Api Client'], 'displayValue': 'Jans Config Api Client', 'value': 'Jans Config Api Client'}], 'customObjectClasses': ['top'], 'rptAsJwt': False, 'accessTokenAsJwt': False, 'accessTokenSigningAlg': 'RS256', 'disabled': False, 'attributes': {'runIntrospectionScriptBeforeAccessTokenAsJwtCreationAndIncludeClaims': False, 'keepClientAuthorizationAfterExpiration': False, 'allowSpontaneousScopes': False, 'backchannelLogoutSessionRequired': False, 'parLifetime': 600, 'requirePar': False, 'jansDefaultPromptLogin': False}, 'displayName': 'Jans Config Api Client', 'authenticationMethod': 'client_secret_basic', 'tokenBindingSupported': False, 'baseDn': 'inum=1800.6c5faab4-dc28-4d46-94f4-df320f7c9780,ou=clients,o=jans', 'inum': '1800.6c5faab4-dc28-4d46-94f4-df320f7c9780'} \ No newline at end of file diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index 9b7d6f461ba..6120c4ab88c 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -22,6 +22,7 @@ VerticalAlign, DynamicContainer, FloatContainer, + Window ) from prompt_toolkit.layout.containers import VerticalAlign from prompt_toolkit.layout.dimension import D @@ -221,6 +222,33 @@ def check_jans_cli_ini(self): else : self.create_cli() + + def jans_creds_dialog(self, *params): + + body=HSplit([ + self.getTitledText("Hostname", name='jans_host', value=config_cli.host or '', jans_help="FQN name of Jannsen Config Api Server"), + self.getTitledText("Client ID", name='jca_client_id', value=config_cli.client_id or '', jans_help="Jannsen Config Api Client ID"), + self.getTitledText("Client Secret", name='jca_client_secret', value=config_cli.client_secret or '', jans_help="Jannsen Config Api Client Secret") + ]) + + buttons = [Button("Save", handler=self.save_creds)] + dialog = JansGDialog(self, title="Janssen Config Api Client Credidentials", body=body, buttons=buttons) + + async def coroutine(): + app = get_app() + focused_before = app.layout.current_window + self.layout.focus(dialog) + result = await self.show_dialog_as_float(dialog) + try: + app.layout.focus(focused_before) + except: + app.layout.focus(self.center_frame) + + self.create_cli() + + ensure_future(coroutine()) + + def dialog_back_but(self): ## BACK self.active_dialog_select = '' self.show_dialog = False @@ -311,19 +339,19 @@ def getTitledText(self, title, name, value='', height=1, jans_help='', width=Non return VSplit([Label(text=li, width=width,style=style), cd], padding=1) - def getTitledCheckBox(self, title, name, values,style=''): - cb = CheckboxList(values=[(o,o) for o in values]) - cb.window.jans_name = name - li,cd,width = self.handle_long_string(title,values,cb) + # def getTitledCheckBox(self, title, name, values,style=''): + # cb = CheckboxList(values=[(o,o) for o in values]) + # cb.window.jans_name = name + # li,cd,width = self.handle_long_string(title,values,cb) - return VSplit([Label(text=li, width=width,style=style,wrap_lines=False), cd]) + # return VSplit([Label(text=li, width=width,style=style,wrap_lines=False), cd]) - def getTitledRadioButton(self, title, name, values,style=''): - rl = RadioList(values=[(option, option) for option in values]) - rl.window.jans_name = name - li,rl2,width = self.handle_long_string(title,values,rl) + # def getTitledRadioButton(self, title, name, values,style=''): + # rl = RadioList(values=[(option, option) for option in values]) + # rl.window.jans_name = name + # li,rl2,width = self.handle_long_string(title,values,rl) - return VSplit([Label(text=li, width=width,style=style), rl2],) + # return VSplit([Label(text=li, width=width,style=style), rl2],) # ----------------------------------------------------------------- # def getButton(self, text, name, jans_help, handler=None): @@ -420,35 +448,15 @@ def save_creds(self, dialog): config_cli.client_id = config_cli.config['DEFAULT']['jca_client_id'] config_cli.client_secret = config_cli.config['DEFAULT']['jca_client_secret'] - def jans_creds_dialog(self, *params): - - body=HSplit([ - self.getTitledText("Hostname", name='jans_host', value=config_cli.host or '', jans_help="FQN name of Jannsen Config Api Server"), - self.getTitledText("Client ID", name='jca_client_id', value=config_cli.client_id or '', jans_help="Jannsen Config Api Client ID"), - self.getTitledText("Client Secret", name='jca_client_secret', value=config_cli.client_secret or '', jans_help="Jannsen Config Api Client Secret") - ]) - - buttons = [Button("Save", handler=self.save_creds)] - dialog = JansGDialog(self, title="Janssen Config Api Client Credidentials", body=body, buttons=buttons) - - async def coroutine(): - app = get_app() - focused_before = app.layout.current_window - self.layout.focus(dialog) - result = await self.show_dialog_as_float(dialog) - try: - app.layout.focus(focused_before) - except: - app.layout.focus(self.center_frame) - - self.create_cli() - - ensure_future(coroutine()) - def edit_client_dialog(self, **params): - dialog = EditClientDialog(self,**params) + + selected_line_data = params['data'] + title = "Edit user Data (Clients)" + + dialog = EditClientDialog(self,title=title,data=selected_line_data) self.show_jans_dialog(dialog) + def edit_scope_dialog(self, **params): dialog = EditScopeDialog(self,**params) self.show_jans_dialog(dialog) diff --git a/jans-cli-tui/models/oauth.py b/jans-cli-tui/models/oauth.py index 3bbafddc0cc..cc21f29b3a8 100644 --- a/jans-cli-tui/models/oauth.py +++ b/jans-cli-tui/models/oauth.py @@ -49,216 +49,9 @@ def initialize(self): self.oauth_containers = {} self.oauth_prepare_navbar() self.oauth_prepare_containers() - self.oauth_prepare_tabs() self.oauth_nav_selection_changed(self.oauth_navbar.navbar_entries[0][0]) - def oauth_prepare_tabs(self): - self.oauth_tabs = {} - self.oauth_tabs['clients'] = OrderedDict() - - self.oauth_tabs['clients']['Basic'] = HSplit([ - VSplit([ - self.getTitledText("Client_ID", name='Client_ID',style='green'), - Window(width=1, char=" ",), - self.getTitledCheckBox("Active:", name='active', values=['active'],style='green'), - ]) , - self.getTitledText("Client Name", name='displayName',style='green'), - self.getTitledText("Client Secret", name='clientSecret',style='green'), - self.getTitledText("Description", name='Description',style='green'), - self.getTitledRadioButton("Id_token Subject Type:", name='Id_token', values=['Pairwise','Public'],style='green'), - self.getTitledCheckBox("Grant:", name='Grant', values=['Authorizatuin Code', 'Refresh Token', 'UMA Ticket','Client Credential','Password','Implicit'],style='green'), - self.getTitledCheckBox("Response Types:", name='responseTypes', values=['code', 'token', 'id_token'],style='green'), - self.getTitledCheckBox("Supress Authorization:", name='Supress', values=['True'],style='green'), - self.getTitledRadioButton("Application Type:", name='applicationType', values=['web', 'native'],style='green'), - self.getTitledText("Redirect Uris", name='redirectUris', height=3,style='green'), - self.getTitledText("Redirect Regex", name='redirectregex',style='green'), - self.getTitledText("Scopes", name='scopes', height=3,style='green'), - # HSplit([Box(body=VSplit([self.yes_button, self.no_button], align="CENTER", padding=3), style="class:button-bar", height=1)], - # height=D(), - # align = VerticalAlign.BOTTOM, - # ) - ],width=D(), - ) - - self.oauth_tabs['clients']['Tokens'] = HSplit([ - self.getTitledRadioButton("Access Token Type", name='access_token_type', values=['JWT','Reference'],style='green'), - self.getTitledCheckBox("Incliude Claims in id_token", name='id_token_claims', values=['True'],style='green'), - self.getTitledCheckBox("Run introspection script before JWT access token creation", name='Supress_JWT', values=['True'],style='green'), - self.getTitledText("Token binding confirmation method for id_token", name='id_token_binding_confirmation',style='green'), - self.getTitledText("Access token additional audiences", name='access_token_audiences',style='green'), - VSplit([ - Button("+", handler=self.show_again,left_symbol='[',right_symbol=']',width=3) - ]), - self.getTitledText("Access token lifetime", name='access_token_lifetime',style='green'), - self.getTitledText("Refresh token lifetime", name='refresh_token_lifetime',style='green'), - self.getTitledText("Defult max authn age", name='max_authn_age',style='green'), - - ],width=D()) - - self.oauth_tabs['clients']['Logout'] = HSplit([ - - self.getTitledText("Front channel logout URI", name='f_channel_logout_URI',style='green'), - self.getTitledText("Post logout redirect URI", name='p_channel_logout_redirect_URI',style='green'), - self.getTitledText("Back channel logout URI", name='b_channel_logout_URI',style='green'), - self.getTitledCheckBox("Back channel logout session required", name='b_channel_session_required', values=['True'],style='green'), - self.getTitledCheckBox("Front channel logout session required", name='f_channel_session_required', values=['True'],style='green'), - - ],width=D() - ) - - self.oauth_tabs['clients']['Software Info'] = HSplit([ - self.getTitledText("Client URI", name='client_URI',style='green'), - self.getTitledText("Policy URI", name='policy_URI',style='green'), - self.getTitledText("Logo URI", name='logo_URI',style='green'), - self.getTitledText("Term of service URI", name='term_of_service_URI',style='green'), - self.getTitledText("Contacts", name='contacts',style='green'), - VSplit([ - Button("+", handler=self.show_again,left_symbol='[',right_symbol=']',width=3,) - ]), - self.getTitledText("Authorized JS origins", name='authorized_JS_origins',style='green'), - VSplit([ - Button("+", handler=self.show_again,left_symbol='[',right_symbol=']',width=3) - ]), - self.getTitledText("Software id", name='software_id',style='green'), - self.getTitledText("Software version", name='software_version',style='green'), - self.getTitledText("Software statement", name='software_statement',style='green'), - - ],width=D()) - - self.oauth_tabs['clients']['CIBA/PAR/UMA'] = HSplit([ - Label(text="CIBA",style='bold'), - self.getTitledRadioButton("Token delivery method", name='applicationType', values=['poll','push', 'ping'],style='green'), - self.getTitledText("Client notification endpoint", name='displayName',style='green'), - self.getTitledCheckBox("Require user code param", name='Supress', values=['True'],style='green'), - - Label(text="PAR",style='bold'), - self.getTitledText("Request lifetime", name='displayName',style='green'), - self.getTitledCheckBox("Request PAR", name='Supress', values=['True'],style='green'), - - Label("UMA",style='bold'), - self.getTitledRadioButton("PRT token type", name='applicationType', values=['JWT', 'Reference'],style='green'), - self.getTitledText("Claims redirect URI", name='displayName',style='green'), - - # self.getButton(text="dropdown1", name='oauth:scopes:dropdown1', jans_help="dropdown1",handler=self.testdropdown), - # self.getButton(text="dropdown2", name='oauth:scopes:dropdown2', jans_help="dropdown2",handler=self.testdropdown), - - Label(text="dropdown1",style='blue'), ## TODO with Jans VerticalNav - Label(text="dropdown2",style='blue'), ## TODO with Jans VerticalNav - Label(text="tabel",style='blue'), ## TODO with Jans VerticalNav - - # JansVerticalNav( - # myparent=self, - # headers=['Client ID', 'Client Name', 'Grant Types', 'Subject Type'], - # preferred_size= [0,0,30,0], - # data=[['1','2','3','4'],['1','2','3','4'],['1','2','3','4'],['1','2','3','4'],], - # on_enter=self.edit_client_dialog, - # on_display=self.data_display_dialog, - # # selection_changed=self.data_selection_changed, - # selectes=0, - # headerColor='green', - # entriesColor='white', - # all_data=[['1','2','3','4'],['1','2','3','4'],['1','2','3','4'],['1','2','3','4'],] - # ) - ] - ) - - self.oauth_tabs['clients']['Encryption/Signing'] = HSplit([ - self.getTitledText("Client JWKS URI", name='displayName',style='green'), - self.getTitledText("Client JWKS", name='displayName',style='green'), - VSplit([ - Label(text="id_token"), - Label(text="a, b, c",style='red'), - ]), - VSplit([ - Label(text="Access token"), - Label(text="a",style='red'), - ]), - VSplit([ - Label(text="Userinfo"), - Label(text="a, b, c",style='red'), - ]), - VSplit([ - Label(text="JARM"), - Label(text="a, b, c",style='red'), - ]), - VSplit([ - Label(text="Request Object"), - Label(text="a, b, c",style='red'), - ]), - - ] - ) - - self.oauth_tabs['clients']['Advanced Client Properties'] = HSplit([ - - self.getTitledCheckBox("Default Prompt=login", name='Supress', values=['True'],style='green'), - VSplit([ - self.getTitledCheckBox("Persist Authorizations", name='Supress', values=['True'],style='green'), - self.getTitledCheckBox("Keep expired?", name='Supress', values=['True'],style='green'), - ]) , - self.getTitledCheckBox("Allow spontaneos scopes", name='Supress', values=['True'],style='green'), - - self.getTitledText("spontaneos scopes validation regex", name='displayName',style='green'), - VSplit([ - Label(text="Spontaneous Scopes",style='green'), - Button("view current", handler=self.show_again,left_symbol='',right_symbol='',) - - ]) , - self.getTitledText("Initial Login URI", name='displayName',style='green'), - - VSplit([ - self.getTitledText("Request URIs", name='clientSecret',style='green'), - Button("+", handler=self.show_again,left_symbol='[',right_symbol=']',width=3,) - ]) , - - Label(text="Dropdown 3",style='blue'), - - VSplit([ - self.getTitledText("Allowed ACRs", name='clientSecret',style='green'), - Button("+", handler=self.show_again,left_symbol='[',right_symbol=']',width=3,) - ]) , - self.getTitledText("TLS Subject DN", name='clientSecret',style='green'), - - VSplit([ - self.getTitledCheckBox("Client Experiation Date", name='id_token_claims', values=['True'],style='green'), - Label(text="Pick Date",style='blue'), - ]) , - - - - ],width=D() - ) - - self.oauth_tabs['clients']['Client Scripts'] = HSplit([ - Label(text="Dropdown 4",style='blue'), - Label(text="Dropdown 5",style='blue'), - Label(text="Dropdown 6",style='blue'), - Label(text="Dropdown 7",style='blue'), - Label(text="Dropdown 8",style='blue'), - ] - ) - - - self.oauth_tabs['clients']['Save'] = HSplit([ - Button("Save", handler=self.show_again,left_symbol='(',right_symbol=')',) - - ],width=D() - ) - - self.oauth_dialog_nav = list(self.oauth_tabs['clients'].keys())[0] - - - def client_dialog_nav_selection_changed(self, selection): - self.oauth_dialog_nav = selection - - def edit_client(self, selected,event,size): ## enter - self.edit_client_dialog() - # self.active_dialog_select = 'enter' - # self.show_dialog = True - - # event.app.layout.focus(self.my_dialogs()) - def oauth_prepare_containers(self): self.oauth_data_container = { @@ -300,23 +93,6 @@ def oauth_prepare_containers(self): height=D(), ) - self.dialogs['oauth:clients:d'] = JansDialog( - entries_list=[[DynamicContainer(lambda:self.data_show_client_dialog) ,'']], - title='Show client Dialog', - button_functions=[[self.dialog_back_but,"Back"]], - height=self.dialog_height, - width=self.dialog_width, - only_view=True - ) - - self.dialogs['oauth:scopes:d'] = JansDialog( - entries_list=[[DynamicContainer(lambda:self.data_show_client_dialog) ,'']], - title='Show scopes Dialog', - button_functions=[[self.dialog_back_but,"Back"]], - height=self.dialog_height, - width=self.dialog_width, - only_view=True - ) def oauth_prepare_navbar(self): self.oauth_navbar = JansNavBar( @@ -340,6 +116,7 @@ def oauth_update_clients(self): try : result = self.cli_object.process_command_by_id('get-oauth-openid-clients', '', 'limit:10', {}) + data =[] for d in result: @@ -357,7 +134,7 @@ def oauth_update_clients(self): headers=['Client ID', 'Client Name', 'Grant Types', 'Subject Type'], preferred_size= [0,0,30,0], data=data, - on_enter=self.edit_client, + on_enter=self.edit_client_dialog, on_display=self.data_display_dialog, # selection_changed=self.data_selection_changed, selectes=0, @@ -402,7 +179,7 @@ def update_oauth_scopes(self): headers=['id', 'Description', 'Type'], preferred_size= [0,0,30,0], data=data, - on_enter=self.edit_scope, + on_enter=self.edit_scope_dialog, on_display=self.data_display_dialog, # selection_changed=self.data_selection_changed, selectes=0, @@ -429,6 +206,3 @@ def oauth_get_scopes(self): def display_scope(self): pass - - def edit_scope(self, selected,event,size): ## enter - self.edit_scope_dialog() \ No newline at end of file diff --git a/jans-cli-tui/mydisct_debug.txt b/jans-cli-tui/mydisct_debug.txt new file mode 100644 index 00000000000..f1a69b96ca5 --- /dev/null +++ b/jans-cli-tui/mydisct_debug.txt @@ -0,0 +1 @@ +{'dn': 'inum=1800.6c5faab4-dc28-4d46-94f4-df320f7c9780,ou=clients,o=jans', 'inum': '1800.6c5faab4-dc28-4d46-94f4-df320f7c9780', 'displayName': 'Jans Config Api Client', 'clientSecret': 'oVSD1QqiLWRW', 'frontChannelLogoutSessionRequired': False, 'redirectUris': ['https://c1.gluu.org/admin-ui', 'http://localhost:4100'], 'responseTypes': ['code'], 'grantTypes': ['authorization_code', 'refresh_token', 'client_credentials'], 'applicationType': 'web', 'logoUri': {}, 'clientUri': {}, 'policyUri': {}, 'tosUri': {}, 'subjectType': 'pairwise', 'idTokenSignedResponseAlg': 'RS256', 'tokenEndpointAuthMethod': 'client_secret_basic', 'requireAuthTime': False, 'scopes': ['inum=C4F7,ou=scopes,o=jans', 'inum=1200.1F13DB,ou=scopes,o=jans', 'inum=1200.E0B4C8,ou=scopes,o=jans', 'inum=1800.590346,ou=scopes,o=jans', 'inum=1800.507006,ou=scopes,o=jans', 'inum=1800.7866C7,ou=scopes,o=jans', 'inum=1800.A1D5CD,ou=scopes,o=jans', 'inum=1800.0714D9,ou=scopes,o=jans', 'inum=1800.32C574,ou=scopes,o=jans', 'inum=1800.C1E748,ou=scopes,o=jans', 'inum=1800.19E881,ou=scopes,o=jans', 'inum=1800.913FF3,ou=scopes,o=jans', 'inum=1800.327D46,ou=scopes,o=jans', 'inum=1800.99C0D3,ou=scopes,o=jans', 'inum=1800.BBD836,ou=scopes,o=jans', 'inum=1800.F8AAB3,ou=scopes,o=jans', 'inum=1800.E03CFE,ou=scopes,o=jans', 'inum=1800.7DC04D,ou=scopes,o=jans', 'inum=1800.1DA5BA,ou=scopes,o=jans', 'inum=1800.926A52,ou=scopes,o=jans', 'inum=1800.FF17D8,ou=scopes,o=jans', 'inum=1800.A650F6,ou=scopes,o=jans', 'inum=1800.580BA9,ou=scopes,o=jans', 'inum=1800.DC882D,ou=scopes,o=jans', 'inum=1800.F97827,ou=scopes,o=jans', 'inum=1800.8FB9E4,ou=scopes,o=jans', 'inum=1800.0EE56D,ou=scopes,o=jans', 'inum=1800.FD8734,ou=scopes,o=jans', 'inum=1800.73E5B5,ou=scopes,o=jans', 'inum=1800.0DE56F,ou=scopes,o=jans', 'inum=1800.F0C44A,ou=scopes,o=jans', 'inum=1800.70AA30,ou=scopes,o=jans', 'inum=1800.632780,ou=scopes,o=jans', 'inum=1800.2646DC,ou=scopes,o=jans', 'inum=1800.54387F,ou=scopes,o=jans', 'inum=1800.16B85E,ou=scopes,o=jans', 'inum=1800.565A62,ou=scopes,o=jans', 'inum=1800.065E1A,ou=scopes,o=jans', 'inum=1800.4E54A5,ou=scopes,o=jans', 'inum=1800.50BBFE,ou=scopes,o=jans', 'inum=1800.6A2BD2,ou=scopes,o=jans', 'inum=1800.B9BE21,ou=scopes,o=jans', 'inum=1800.A8FD82,ou=scopes,o=jans', 'inum=1800.BDA282,ou=scopes,o=jans', 'inum=1800.0B7536,ou=scopes,o=jans', 'inum=1800.8100D9,ou=scopes,o=jans', 'inum=1800.987C52,ou=scopes,o=jans', 'inum=1800.DB17D4,ou=scopes,o=jans', 'inum=1800.AA15F1,ou=scopes,o=jans', 'inum=1800.5B28B1,ou=scopes,o=jans', 'inum=1800.53B808,ou=scopes,o=jans', 'inum=1800.573EBC,ou=scopes,o=jans', 'inum=2762b464-0b67-4071-8072-91cb371895a6,ou=scopes,o=jans', 'inum=378ac51e-6746-4b2c-a9dc-ca3261f998e1,ou=scopes,o=jans', 'inum=478e15ec-1871-4314-a909-3d2c84bd09f9,ou=scopes,o=jans'], 'trustedClient': False, 'persistClientAuthorizations': True, 'includeClaimsInIdToken': False, 'customAttributes': [{'name': 'displayName', 'multiValued': False, 'values': ['Jans Config Api Client'], 'displayValue': 'Jans Config Api Client', 'value': 'Jans Config Api Client'}], 'customObjectClasses': ['top'], 'rptAsJwt': False, 'accessTokenAsJwt': False, 'accessTokenSigningAlg': 'RS256', 'disabled': False, 'attributes': {'runIntrospectionScriptBeforeAccessTokenAsJwtCreationAndIncludeClaims': False, 'keepClientAuthorizationAfterExpiration': False, 'allowSpontaneousScopes': False, 'backchannelLogoutSessionRequired': False, 'parLifetime': 600, 'requirePar': False, 'jansDefaultPromptLogin': False}, 'deletable': False} \ No newline at end of file diff --git a/jans-cli-tui/radio.txt b/jans-cli-tui/radio.txt new file mode 100644 index 00000000000..3c5ea725980 --- /dev/null +++ b/jans-cli-tui/radio.txt @@ -0,0 +1,10 @@ +Name : subjectTypesSupported , value = +********** +Name : applicationType , value = web +********** +Name : accessTokenAsJwt , value = False +********** +Name : backchannelTokenDeliveryMode , value = +********** +Name : applicationType! , value = +********** diff --git a/jans-cli-tui/wui_components/edit_client_dialog.py b/jans-cli-tui/wui_components/edit_client_dialog.py index ee47573806b..c1ca2048b55 100644 --- a/jans-cli-tui/wui_components/edit_client_dialog.py +++ b/jans-cli-tui/wui_components/edit_client_dialog.py @@ -41,18 +41,17 @@ ) from wui_components.jans_dialog_with_nav import JansDialogWithNav from wui_components.jans_side_nav_bar import JansSideNavBar +from wui_components.jans_cli_dialog import JansGDialog +import yaml -class EditClientDialog: - def __init__(self,myparent, **params): - self.myparent = myparent - self.future = Future() - - def accept_text(buf): - get_app().layout.focus(ok_button) - buf.complete_state = None - return True +class EditClientDialog(JansGDialog): + def __init__(self, parent, title, data ,buttons=...): + super().__init__(parent, title, buttons) + self.data = data + self.prepare_tabs() + def accept(): self.future.set_result(DialogResult.ACCEPT) @@ -60,15 +59,15 @@ def cancel(): self.future.set_result(DialogResult.CANCEL) self.side_NavBar = JansSideNavBar(myparent=self.myparent, - entries=list(self.myparent.oauth_tabs['clients'].keys()), - selection_changed=(self.myparent.client_dialog_nav_selection_changed) , + entries=list(self.oauth_tabs['clients'].keys()), + selection_changed=(self.client_dialog_nav_selection_changed) , select=0, entries_color='#2600ff') self.dialog = JansDialogWithNav( - title="Edit user Data (Clients)", + title=title, navbar=DynamicContainer(lambda:self.side_NavBar), - content=DynamicContainer(lambda: self.myparent.oauth_tabs['clients'][self.myparent.oauth_dialog_nav]), + content=DynamicContainer(lambda: self.oauth_tabs['clients'][self.oauth_dialog_nav]), button_functions=[ (accept, "Save"), (cancel, "Cancel") @@ -77,14 +76,247 @@ def cancel(): width=self.myparent.dialog_width, ) + def prepare_tabs(self): + self.oauth_tabs = {} + self.oauth_tabs['clients'] = OrderedDict() + + self.oauth_tabs['clients']['Basic'] = HSplit([ + VSplit([ + self.getTitledText(title ="Client_ID", name='inum',style='green',), + Window(width=1, char=" ",), + self.getTitledCheckBox("Active:", name='disabled', values=['active'],style='green'), + ]) , + self.getTitledText("Client Name", name='displayName',style='green'), + self.getTitledText("Client Secret", name='clientSecret',style='green'), + self.getTitledText("Description", name='description',style='green'), + Label(text="dropdown1",style='blue'), + self.getTitledRadioButton("Id_token Subject Type:", name='subjectTypesSupported', values=['Public','Pairwise'],style='green'), + self.getTitledCheckBox("Grant:", name='grantTypes', values=['Authorizatuin Code', 'Refresh Token', 'UMA Ticket','Client Credential','Password','Implicit'],style='green'), + self.getTitledCheckBox("Response Types:", name='responseTypes', values=['code', 'token', 'id_token'],style='green'), + self.getTitledCheckBox("Supress Authorization:", name='dynamicRegistrationPersistClientAuthorizations', values=['True'],style='green'), + self.getTitledRadioButton("Application Type:", name='applicationType', values=[ 'native','web'],style='green'), + self.getTitledText("Redirect Uris", name='redirectUris', height=3,style='green'), + self.getTitledText("Redirect Regex", name='redirectregex',style='green'), + self.getTitledText("Scopes", name='scopes', height=3,style='green'), + + ],width=D(), + ) + + self.oauth_tabs['clients']['Tokens'] = HSplit([ + self.getTitledRadioButton("Access Token Type", name='accessTokenAsJwt', values=['JWT','Reference'],style='green'), + self.getTitledCheckBox("Incliude Claims in id_token", name='includeClaimsInIdToken', values=['True'],style='green'), + self.getTitledCheckBox("Run introspection script before JWT access token creation", name='runIntrospectionScriptBeforeAccessTokenAsJwtCreationAndIncludeClaims', values=['True'],style='green'), + self.getTitledText("Token binding confirmation method for id_token", name='idTokenTokenBindingCnf',style='green'), + self.getTitledText("Access token additional audiences", name='additionalAudience',style='green'), + VSplit([ + Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3) + ]), + self.getTitledText("Access token lifetime", name='accessTokenLifetime',style='green'), + self.getTitledText("Refresh token lifetime", name='refreshTokenLifetime',style='green'), + self.getTitledText("Defult max authn age", name='defaultMaxAge',style='green'), + + ],width=D()) + + self.oauth_tabs['clients']['Logout'] = HSplit([ + + self.getTitledText("Front channel logout URI", name='frontChannelLogoutUri',style='green'), + self.getTitledText("Post logout redirect URI", name='postLogoutRedirectUris',style='green'), + self.getTitledText("Back channel logout URI", name='backchannelLogoutUri',style='green'), + self.getTitledCheckBox("Back channel logout session required", name='backchannelLogoutSessionRequired', values=['True'],style='green'), + self.getTitledCheckBox("Front channel logout session required", name='frontChannelLogoutSessionRequired', values=['True'],style='green'), + + ],width=D() + ) + + self.oauth_tabs['clients']['Software Info'] = HSplit([ + self.getTitledText("Client URI", name='clientUri',style='green'), + self.getTitledText("Policy URI", name='policyUri',style='green'), + self.getTitledText("Logo URI", name='logoUri',style='green'), + self.getTitledText("Term of service URI", name='term_of_service_URI',style='green'), + self.getTitledText("Contacts", name='contacts',style='green'), + VSplit([ + Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3,) + ]), + self.getTitledText("Authorized JS origins", name='authorizedOrigins',style='green'), + VSplit([ + Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3) + ]), + self.getTitledText("Software id", name='softwareId',style='green'), + self.getTitledText("Software version", name='softwareVersion',style='green'), + self.getTitledText("Software statement", name='softwareStatement',style='green'), + + ],width=D()) + + self.oauth_tabs['clients']['CIBA/PAR/UMA'] = HSplit([ + Label(text="CIBA",style='bold'), + self.getTitledRadioButton("Token delivery method", name='backchannelTokenDeliveryMode', values=['poll','push', 'ping'],style='green'), + self.getTitledText("Client notification endpoint", name='backchannelClientNotificationEndpoint',style='green'), + self.getTitledCheckBox("Require user code param", name='backchannelUserCodeParameterSupported', values=['True'],style='green'), + + Label(text="PAR",style='bold'), + self.getTitledText("Request lifetime", name='request!',style='green'), + self.getTitledCheckBox("Request PAR", name='sessionIdRequestParameterEnabled', values=['True'],style='green'), + + Label("UMA",style='bold'), + self.getTitledRadioButton("PRT token type", name='applicationType!', values=['JWT', 'Reference'],style='green'), + self.getTitledText("Claims redirect URI", name='claims!',style='green'), + + # self.myparent.getButton(text="dropdown1", name='oauth:scopes:dropdown1', jans_help="dropdown1",handler=self.myparent.testdropdown), + # self.myparent.getButton(text="dropdown2", name='oauth:scopes:dropdown2', jans_help="dropdown2",handler=self.myparent.testdropdown), + + Label(text="dropdown1",style='blue'), ## TODO with Jans VerticalNav + Label(text="dropdown2",style='blue'), ## TODO with Jans VerticalNav + Label(text="tabel",style='blue'), ## TODO with Jans VerticalNav + + ] + ) + + self.oauth_tabs['clients']['Encryption/Signing'] = HSplit([ + self.getTitledText("Client JWKS URI", name='jwksUri',style='green'), + self.getTitledText("Client JWKS", name='jwks',style='green'), + VSplit([ + Label(text="id_token"), + Label(text="a, b, c",style='red'), + ]), + VSplit([ + Label(text="Access token"), + Label(text="a",style='red'), + ]), + VSplit([ + Label(text="Userinfo"), + Label(text="a, b, c",style='red'), + ]), + VSplit([ + Label(text="JARM"), + Label(text="a, b, c",style='red'), + ]), + VSplit([ + Label(text="Request Object"), + Label(text="a, b, c",style='red'), + ]), + + ] + ) + + self.oauth_tabs['clients']['Advanced Client Properties'] = HSplit([ + + self.getTitledCheckBox("Default Prompt=login", name='defaultPromptLogin', values=['True'],style='green'), + VSplit([ + self.getTitledCheckBox("Persist Authorizations", name='persistClientAuthorizations', values=['True'],style='green'), + self.getTitledCheckBox("Keep expired?", name='Supress', values=['True'],style='green'), + ]) , + self.getTitledCheckBox("Allow spontaneos scopes", name='allowSpontaneousScopes', values=['True'],style='green'), + + self.getTitledText("spontaneos scopes validation regex", name='spontaneousScopes',style='green'), + VSplit([ + Label(text="Spontaneous Scopes",style='green'), + Button("view current", handler=self.myparent.show_again,left_symbol='',right_symbol='',) + + ]) , + self.getTitledText("Initial Login URI", name='initiateLoginUri',style='green'), + + VSplit([ + self.getTitledText("Request URIs", name='requestUris',style='green'), + Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3,) + ]) , + + Label(text="Dropdown 3",style='blue'), + + VSplit([ + self.getTitledText("Allowed ACRs", name='clientSecret',style='green'), + Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3,) + ]) , + self.getTitledText("TLS Subject DN", name='clientSecret',style='green'), + + VSplit([ + self.getTitledCheckBox("Client Experiation Date", name='id_token_claims', values=['True'],style='green'), + Label(text="Pick Date",style='blue'), + ]) , + + + + ],width=D() + ) + + self.oauth_tabs['clients']['Client Scripts'] = HSplit([ + Label(text="Dropdown 4",style='blue'), + Label(text="Dropdown 5",style='blue'), + Label(text="Dropdown 6",style='blue'), + Label(text="Dropdown 7",style='blue'), + Label(text="Dropdown 8",style='blue'), + ] + ) + + self.oauth_tabs['clients']['Save'] = HSplit([ + Button("Save", handler=self.myparent.show_again,left_symbol='(',right_symbol=')',) + + ],width=D() + ) + + self.oauth_dialog_nav = list(self.oauth_tabs['clients'].keys())[0] + + def getTitledText(self, title, name, value='', height=1, jans_help='', width=None,style='',data=[]): + ### Custom getTitledText (i didnt want to mess with the old one) + ### search for getTitledText widget name in `data` + ### data is Dict + + if name in self.data.keys() : + if type(self.data[name]) is not list : + value= str(self.data[name]) + else : + value= str('\n'.join(self.data[name])) +# + multiline = height > 1 + ta = TextArea(text=value, multiline=multiline,style="class:titledtext") + ta.window.jans_name = name + ta.window.jans_help = jans_help + li,cd,width = self.myparent.handle_long_string(title,[1]*height,ta) + + return VSplit([Label(text=li, width=width,style=style), cd], padding=1) + + + def getTitledCheckBox(self, title, name, values,style=''): + + value = '' + if name in self.data.keys() : + if type(self.data[name]) is not list : + value= str(self.data[name]) + + with open('./checkbox.txt', 'a') as f: + f.write("Name : {} , value = {}".format(name,value)) + f.write("\n**********\n") + + + cb = CheckboxList(values=[(o,o) for o in values],default_values=value) + cb.window.jans_name = name + li,cd,width = self.myparent.handle_long_string(title,values,cb) + + return VSplit([Label(text=li, width=width,style=style,wrap_lines=False), cd]) + + def getTitledRadioButton(self, title, name, values,style=''): + value = '' + if name in self.data.keys() : + if type(self.data[name]) is not list : + value= str(self.data[name]) + + # if value == 'False' : + # value = '' + # elif value == 'True': + + with open('./radio.txt', 'a') as f: + f.write("Name : {} , value = {}".format(name,value)) + f.write("\n**********\n") + + rl = RadioList(values=[(option, option) for option in values],default=value) + rl.window.jans_name = name + li,rl2,width = self.myparent.handle_long_string(title,values,rl) + + return VSplit([Label(text=li, width=width,style=style), rl2],) - ok_button = Button(text="OK", handler=accept) - cancel_button = Button(text="Cancel", handler=cancel) - buttons = [cancel_button] - #if params.get('ok_button'): - buttons.insert(0, ok_button) + def client_dialog_nav_selection_changed(self, selection): + self.oauth_dialog_nav = selection def __pt_container__(self): return self.dialog diff --git a/jans-cli-tui/wui_components/jans_vetrical_nav.py b/jans-cli-tui/wui_components/jans_vetrical_nav.py index 9f8179c134d..3069f88ca62 100644 --- a/jans-cli-tui/wui_components/jans_vetrical_nav.py +++ b/jans-cli-tui/wui_components/jans_vetrical_nav.py @@ -173,7 +173,7 @@ def _go_up(event) -> None: def _(event): passed = [i.strip() for i in self.data[self.selectes]] size = self.myparent.output.get_size() - self.on_enter(passed,event,size) + self.on_enter(passed=passed,event=event,size=size,data=self.all_data[self.selectes]) @kb.add("d") From ba0052838edacbb61c93c6b6a8393088e3f77d5a Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Wed, 17 Aug 2022 09:07:54 -0700 Subject: [PATCH 022/364] feat: jans-cli populate data to clients tabs --- jans-cli-tui/wui_components/edit_client_dialog.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/jans-cli-tui/wui_components/edit_client_dialog.py b/jans-cli-tui/wui_components/edit_client_dialog.py index c1ca2048b55..6c063b152cf 100644 --- a/jans-cli-tui/wui_components/edit_client_dialog.py +++ b/jans-cli-tui/wui_components/edit_client_dialog.py @@ -299,10 +299,6 @@ def getTitledRadioButton(self, title, name, values,style=''): if type(self.data[name]) is not list : value= str(self.data[name]) - # if value == 'False' : - # value = '' - # elif value == 'True': - with open('./radio.txt', 'a') as f: f.write("Name : {} , value = {}".format(name,value)) f.write("\n**********\n") From 29a5f4844dfae3f5b7c0114da468c7d35bdd1f5e Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Wed, 17 Aug 2022 20:03:47 +0300 Subject: [PATCH 023/364] fix: jans-cli remove merge comments --- jans-cli-tui/checkbox.txt | 30 ++++++++++++++++++++++++++++++ jans-cli-tui/models/oauth.py | 4 +--- jans-cli-tui/radio.txt | 10 ++++++++++ 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/jans-cli-tui/checkbox.txt b/jans-cli-tui/checkbox.txt index 878cadd8163..d04a343f965 100644 --- a/jans-cli-tui/checkbox.txt +++ b/jans-cli-tui/checkbox.txt @@ -28,3 +28,33 @@ Name : allowSpontaneousScopes , value = ********** Name : id_token_claims , value = ********** +Name : disabled , value = False +********** +Name : grantTypes , value = +********** +Name : responseTypes , value = +********** +Name : dynamicRegistrationPersistClientAuthorizations , value = +********** +Name : includeClaimsInIdToken , value = False +********** +Name : runIntrospectionScriptBeforeAccessTokenAsJwtCreationAndIncludeClaims , value = +********** +Name : backchannelLogoutSessionRequired , value = +********** +Name : frontChannelLogoutSessionRequired , value = False +********** +Name : backchannelUserCodeParameterSupported , value = +********** +Name : sessionIdRequestParameterEnabled , value = +********** +Name : defaultPromptLogin , value = +********** +Name : persistClientAuthorizations , value = False +********** +Name : Supress , value = +********** +Name : allowSpontaneousScopes , value = +********** +Name : id_token_claims , value = +********** diff --git a/jans-cli-tui/models/oauth.py b/jans-cli-tui/models/oauth.py index 9abf3d916ec..70169367b07 100644 --- a/jans-cli-tui/models/oauth.py +++ b/jans-cli-tui/models/oauth.py @@ -214,8 +214,6 @@ def oauth_get_scopes(self): def display_scope(self): pass -<<<<<<< HEAD -======= def edit_scope(self, selected,event,size): ## enter - self.edit_scope_dialog() \ No newline at end of file + self.edit_scope_dialog() diff --git a/jans-cli-tui/radio.txt b/jans-cli-tui/radio.txt index 3c5ea725980..fc17df55040 100644 --- a/jans-cli-tui/radio.txt +++ b/jans-cli-tui/radio.txt @@ -8,3 +8,13 @@ Name : backchannelTokenDeliveryMode , value = ********** Name : applicationType! , value = ********** +Name : subjectTypesSupported , value = +********** +Name : applicationType , value = native +********** +Name : accessTokenAsJwt , value = False +********** +Name : backchannelTokenDeliveryMode , value = +********** +Name : applicationType! , value = +********** From bffba2a1092c39df7103827235007a804762750b Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Wed, 17 Aug 2022 20:06:28 +0300 Subject: [PATCH 024/364] fix: jans-cli remove debug files --- jans-cli-tui/checkbox.txt | 60 ------------------ jans-cli-tui/data_debug.txt | 108 --------------------------------- jans-cli-tui/hopa.txt | 1 - jans-cli-tui/mydisct_debug.txt | 1 - jans-cli-tui/radio.txt | 20 ------ 5 files changed, 190 deletions(-) delete mode 100644 jans-cli-tui/checkbox.txt delete mode 100644 jans-cli-tui/data_debug.txt delete mode 100644 jans-cli-tui/hopa.txt delete mode 100644 jans-cli-tui/mydisct_debug.txt delete mode 100644 jans-cli-tui/radio.txt diff --git a/jans-cli-tui/checkbox.txt b/jans-cli-tui/checkbox.txt deleted file mode 100644 index d04a343f965..00000000000 --- a/jans-cli-tui/checkbox.txt +++ /dev/null @@ -1,60 +0,0 @@ -Name : disabled , value = False -********** -Name : grantTypes , value = -********** -Name : responseTypes , value = -********** -Name : dynamicRegistrationPersistClientAuthorizations , value = -********** -Name : includeClaimsInIdToken , value = False -********** -Name : runIntrospectionScriptBeforeAccessTokenAsJwtCreationAndIncludeClaims , value = -********** -Name : backchannelLogoutSessionRequired , value = -********** -Name : frontChannelLogoutSessionRequired , value = False -********** -Name : backchannelUserCodeParameterSupported , value = -********** -Name : sessionIdRequestParameterEnabled , value = -********** -Name : defaultPromptLogin , value = -********** -Name : persistClientAuthorizations , value = True -********** -Name : Supress , value = -********** -Name : allowSpontaneousScopes , value = -********** -Name : id_token_claims , value = -********** -Name : disabled , value = False -********** -Name : grantTypes , value = -********** -Name : responseTypes , value = -********** -Name : dynamicRegistrationPersistClientAuthorizations , value = -********** -Name : includeClaimsInIdToken , value = False -********** -Name : runIntrospectionScriptBeforeAccessTokenAsJwtCreationAndIncludeClaims , value = -********** -Name : backchannelLogoutSessionRequired , value = -********** -Name : frontChannelLogoutSessionRequired , value = False -********** -Name : backchannelUserCodeParameterSupported , value = -********** -Name : sessionIdRequestParameterEnabled , value = -********** -Name : defaultPromptLogin , value = -********** -Name : persistClientAuthorizations , value = False -********** -Name : Supress , value = -********** -Name : allowSpontaneousScopes , value = -********** -Name : id_token_claims , value = -********** diff --git a/jans-cli-tui/data_debug.txt b/jans-cli-tui/data_debug.txt deleted file mode 100644 index 51e44947a25..00000000000 --- a/jans-cli-tui/data_debug.txt +++ /dev/null @@ -1,108 +0,0 @@ - - - -{'dn': 'inum=1800.6c5faab4-dc28-4d46-94f4-df320f7c9780, -ou=clients,o=jans', - 'deletable': False, - 'clientSecret': 'oVSD1QqiLWRW', - 'frontChannelLogoutSessionRequired': False, - 'redirectUris': ['https://c1.gluu.org/admin-ui', 'http://localhost:4100'], - 'responseTypes': ['code'], - 'grantTypes': ['authorization_code', 'refresh_token', 'client_credentials'], - 'applicationType': 'web', - 'clientName': {'values': {'': 'Jans Config Api Client'}, - 'languageTags': [''], - 'value': 'Jans Config Api Client'}, - 'logoUri': {}, - 'clientUri': {}, - 'policyUri': {}, - 'tosUri': {}, - 'subjectType': 'pairwise', - 'idTokenSignedResponseAlg': 'RS256', - 'tokenEndpointAuthMethod': 'client_secret_basic', - 'requireAuthTime': False, - 'scopes': ['inum=C4F7,ou=scopes,o=jans', - 'inum=1200.1F13DB,ou=scopes,o=jans', - 'inum=1200.E0B4C8,ou=scopes,o=jans', - 'inum=1800.590346,ou=scopes,o=jans', - 'inum=1800.507006,ou=scopes,o=jans', - 'inum=1800.7866C7,ou=scopes,o=jans', - 'inum=1800.A1D5CD,ou=scopes,o=jans', - 'inum=1800.0714D9,ou=scopes,o=jans', - 'inum=1800.32C574,ou=scopes,o=jans', - 'inum=1800.C1E748,ou=scopes,o=jans', - 'inum=1800.19E881,ou=scopes,o=jans', - 'inum=1800.913FF3,ou=scopes,o=jans', - 'inum=1800.327D46,ou=scopes,o=jans', - 'inum=1800.99C0D3,ou=scopes,o=jans', - 'inum=1800.BBD836,ou=scopes,o=jans', - 'inum=1800.F8AAB3,ou=scopes,o=jans', - 'inum=1800.E03CFE,ou=scopes,o=jans', - 'inum=1800.7DC04D,ou=scopes,o=jans', - 'inum=1800.1DA5BA,ou=scopes,o=jans', - 'inum=1800.926A52,ou=scopes,o=jans', - 'inum=1800.FF17D8,ou=scopes,o=jans', - 'inum=1800.A650F6,ou=scopes,o=jans', - 'inum=1800.580BA9,ou=scopes,o=jans', - 'inum=1800.DC882D,ou=scopes,o=jans', - 'inum=1800.F97827,ou=scopes,o=jans', - 'inum=1800.8FB9E4,ou=scopes,o=jans', - 'inum=1800.0EE56D,ou=scopes,o=jans', - 'inum=1800.FD8734,ou=scopes,o=jans', - 'inum=1800.73E5B5,ou=scopes,o=jans', - 'inum=1800.0DE56F,ou=scopes,o=jans', - 'inum=1800.F0C44A,ou=scopes,o=jans', - 'inum=1800.70AA30,ou=scopes,o=jans', - 'inum=1800.632780,ou=scopes,o=jans', - 'inum=1800.2646DC,ou=scopes,o=jans', - 'inum=1800.54387F,ou=scopes,o=jans', - 'inum=1800.16B85E,ou=scopes,o=jans', - 'inum=1800.565A62,ou=scopes,o=jans', - 'inum=1800.065E1A,ou=scopes,o=jans', - 'inum=1800.4E54A5,ou=scopes,o=jans', - 'inum=1800.50BBFE,ou=scopes,o=jans', - 'inum=1800.6A2BD2,ou=scopes,o=jans', - 'inum=1800.B9BE21,ou=scopes,o=jans', - 'inum=1800.A8FD82,ou=scopes,o=jans', - 'inum=1800.BDA282,ou=scopes,o=jans', - 'inum=1800.0B7536,ou=scopes,o=jans', - 'inum=1800.8100D9,ou=scopes,o=jans', - 'inum=1800.987C52,ou=scopes,o=jans', - 'inum=1800.DB17D4,ou=scopes,o=jans', - 'inum=1800.AA15F1,ou=scopes,o=jans', - 'inum=1800.5B28B1,ou=scopes,o=jans', - 'inum=1800.53B808,ou=scopes,o=jans', - 'inum=1800.573EBC,ou=scopes,o=jans', - 'inum=2762b464-0b67-4071-8072-91cb371895a6, - ou=scopes,o=jans', - 'inum=378ac51e-6746-4b2c-a9dc-ca3261f998e1, - ou=scopes,o=jans', - 'inum=478e15ec-1871-4314-a909-3d2c84bd09f9, - ou=scopes,o=jans'], - 'trustedClient': False, - 'persistClientAuthorizations': True, - 'includeClaimsInIdToken': False, - 'customAttributes': [{'name': 'displayName', - 'multiValued': False, - 'values': ['Jans Config Api Client'], - 'displayValue': 'Jans Config Api Client', - 'value': 'Jans Config Api Client'}], - 'customObjectClasses': ['top'], - 'rptAsJwt': False, - 'accessTokenAsJwt': False, - 'accessTokenSigningAlg': 'RS256', - 'disabled': False, - 'attributes': {'runIntrospectionScriptBeforeAccessTokenAsJwtCreationAndIncludeClaims': False, - 'keepClientAuthorizationAfterExpiration': False, - 'allowSpontaneousScopes': False, - 'backchannelLogoutSessionRequired': False, - 'parLifetime': 600, - 'requirePar': False, - 'jansDefaultPromptLogin': False}, - 'displayName': 'Jans Config Api Client', - 'authenticationMethod': 'client_secret_basic', - 'tokenBindingSupported': False, - 'baseDn': 'inum=1800.6c5faab4-dc28-4d46-94f4-df320f7c9780, - ou=clients,o=jans', - 'inum': '1800.6c5faab4-dc28-4d46-94f4-df320f7c9780' - } diff --git a/jans-cli-tui/hopa.txt b/jans-cli-tui/hopa.txt deleted file mode 100644 index 22ff299c575..00000000000 --- a/jans-cli-tui/hopa.txt +++ /dev/null @@ -1 +0,0 @@ -{'dn': 'inum=1800.6c5faab4-dc28-4d46-94f4-df320f7c9780,ou=clients,o=jans', 'deletable': False, 'clientSecret': 'oVSD1QqiLWRW', 'frontChannelLogoutSessionRequired': False, 'redirectUris': ['https://c1.gluu.org/admin-ui', 'http://localhost:4100'], 'responseTypes': ['code'], 'grantTypes': ['authorization_code', 'refresh_token', 'client_credentials'], 'applicationType': 'web', 'clientName': {'values': {'': 'Jans Config Api Client'}, 'languageTags': [''], 'value': 'Jans Config Api Client'}, 'logoUri': {}, 'clientUri': {}, 'policyUri': {}, 'tosUri': {}, 'subjectType': 'pairwise', 'idTokenSignedResponseAlg': 'RS256', 'tokenEndpointAuthMethod': 'client_secret_basic', 'requireAuthTime': False, 'scopes': ['inum=C4F7,ou=scopes,o=jans', 'inum=1200.1F13DB,ou=scopes,o=jans', 'inum=1200.E0B4C8,ou=scopes,o=jans', 'inum=1800.590346,ou=scopes,o=jans', 'inum=1800.507006,ou=scopes,o=jans', 'inum=1800.7866C7,ou=scopes,o=jans', 'inum=1800.A1D5CD,ou=scopes,o=jans', 'inum=1800.0714D9,ou=scopes,o=jans', 'inum=1800.32C574,ou=scopes,o=jans', 'inum=1800.C1E748,ou=scopes,o=jans', 'inum=1800.19E881,ou=scopes,o=jans', 'inum=1800.913FF3,ou=scopes,o=jans', 'inum=1800.327D46,ou=scopes,o=jans', 'inum=1800.99C0D3,ou=scopes,o=jans', 'inum=1800.BBD836,ou=scopes,o=jans', 'inum=1800.F8AAB3,ou=scopes,o=jans', 'inum=1800.E03CFE,ou=scopes,o=jans', 'inum=1800.7DC04D,ou=scopes,o=jans', 'inum=1800.1DA5BA,ou=scopes,o=jans', 'inum=1800.926A52,ou=scopes,o=jans', 'inum=1800.FF17D8,ou=scopes,o=jans', 'inum=1800.A650F6,ou=scopes,o=jans', 'inum=1800.580BA9,ou=scopes,o=jans', 'inum=1800.DC882D,ou=scopes,o=jans', 'inum=1800.F97827,ou=scopes,o=jans', 'inum=1800.8FB9E4,ou=scopes,o=jans', 'inum=1800.0EE56D,ou=scopes,o=jans', 'inum=1800.FD8734,ou=scopes,o=jans', 'inum=1800.73E5B5,ou=scopes,o=jans', 'inum=1800.0DE56F,ou=scopes,o=jans', 'inum=1800.F0C44A,ou=scopes,o=jans', 'inum=1800.70AA30,ou=scopes,o=jans', 'inum=1800.632780,ou=scopes,o=jans', 'inum=1800.2646DC,ou=scopes,o=jans', 'inum=1800.54387F,ou=scopes,o=jans', 'inum=1800.16B85E,ou=scopes,o=jans', 'inum=1800.565A62,ou=scopes,o=jans', 'inum=1800.065E1A,ou=scopes,o=jans', 'inum=1800.4E54A5,ou=scopes,o=jans', 'inum=1800.50BBFE,ou=scopes,o=jans', 'inum=1800.6A2BD2,ou=scopes,o=jans', 'inum=1800.B9BE21,ou=scopes,o=jans', 'inum=1800.A8FD82,ou=scopes,o=jans', 'inum=1800.BDA282,ou=scopes,o=jans', 'inum=1800.0B7536,ou=scopes,o=jans', 'inum=1800.8100D9,ou=scopes,o=jans', 'inum=1800.987C52,ou=scopes,o=jans', 'inum=1800.DB17D4,ou=scopes,o=jans', 'inum=1800.AA15F1,ou=scopes,o=jans', 'inum=1800.5B28B1,ou=scopes,o=jans', 'inum=1800.53B808,ou=scopes,o=jans', 'inum=1800.573EBC,ou=scopes,o=jans', 'inum=2762b464-0b67-4071-8072-91cb371895a6,ou=scopes,o=jans', 'inum=378ac51e-6746-4b2c-a9dc-ca3261f998e1,ou=scopes,o=jans', 'inum=478e15ec-1871-4314-a909-3d2c84bd09f9,ou=scopes,o=jans'], 'trustedClient': False, 'persistClientAuthorizations': True, 'includeClaimsInIdToken': False, 'customAttributes': [{'name': 'displayName', 'multiValued': False, 'values': ['Jans Config Api Client'], 'displayValue': 'Jans Config Api Client', 'value': 'Jans Config Api Client'}], 'customObjectClasses': ['top'], 'rptAsJwt': False, 'accessTokenAsJwt': False, 'accessTokenSigningAlg': 'RS256', 'disabled': False, 'attributes': {'runIntrospectionScriptBeforeAccessTokenAsJwtCreationAndIncludeClaims': False, 'keepClientAuthorizationAfterExpiration': False, 'allowSpontaneousScopes': False, 'backchannelLogoutSessionRequired': False, 'parLifetime': 600, 'requirePar': False, 'jansDefaultPromptLogin': False}, 'displayName': 'Jans Config Api Client', 'authenticationMethod': 'client_secret_basic', 'tokenBindingSupported': False, 'baseDn': 'inum=1800.6c5faab4-dc28-4d46-94f4-df320f7c9780,ou=clients,o=jans', 'inum': '1800.6c5faab4-dc28-4d46-94f4-df320f7c9780'} \ No newline at end of file diff --git a/jans-cli-tui/mydisct_debug.txt b/jans-cli-tui/mydisct_debug.txt deleted file mode 100644 index f1a69b96ca5..00000000000 --- a/jans-cli-tui/mydisct_debug.txt +++ /dev/null @@ -1 +0,0 @@ -{'dn': 'inum=1800.6c5faab4-dc28-4d46-94f4-df320f7c9780,ou=clients,o=jans', 'inum': '1800.6c5faab4-dc28-4d46-94f4-df320f7c9780', 'displayName': 'Jans Config Api Client', 'clientSecret': 'oVSD1QqiLWRW', 'frontChannelLogoutSessionRequired': False, 'redirectUris': ['https://c1.gluu.org/admin-ui', 'http://localhost:4100'], 'responseTypes': ['code'], 'grantTypes': ['authorization_code', 'refresh_token', 'client_credentials'], 'applicationType': 'web', 'logoUri': {}, 'clientUri': {}, 'policyUri': {}, 'tosUri': {}, 'subjectType': 'pairwise', 'idTokenSignedResponseAlg': 'RS256', 'tokenEndpointAuthMethod': 'client_secret_basic', 'requireAuthTime': False, 'scopes': ['inum=C4F7,ou=scopes,o=jans', 'inum=1200.1F13DB,ou=scopes,o=jans', 'inum=1200.E0B4C8,ou=scopes,o=jans', 'inum=1800.590346,ou=scopes,o=jans', 'inum=1800.507006,ou=scopes,o=jans', 'inum=1800.7866C7,ou=scopes,o=jans', 'inum=1800.A1D5CD,ou=scopes,o=jans', 'inum=1800.0714D9,ou=scopes,o=jans', 'inum=1800.32C574,ou=scopes,o=jans', 'inum=1800.C1E748,ou=scopes,o=jans', 'inum=1800.19E881,ou=scopes,o=jans', 'inum=1800.913FF3,ou=scopes,o=jans', 'inum=1800.327D46,ou=scopes,o=jans', 'inum=1800.99C0D3,ou=scopes,o=jans', 'inum=1800.BBD836,ou=scopes,o=jans', 'inum=1800.F8AAB3,ou=scopes,o=jans', 'inum=1800.E03CFE,ou=scopes,o=jans', 'inum=1800.7DC04D,ou=scopes,o=jans', 'inum=1800.1DA5BA,ou=scopes,o=jans', 'inum=1800.926A52,ou=scopes,o=jans', 'inum=1800.FF17D8,ou=scopes,o=jans', 'inum=1800.A650F6,ou=scopes,o=jans', 'inum=1800.580BA9,ou=scopes,o=jans', 'inum=1800.DC882D,ou=scopes,o=jans', 'inum=1800.F97827,ou=scopes,o=jans', 'inum=1800.8FB9E4,ou=scopes,o=jans', 'inum=1800.0EE56D,ou=scopes,o=jans', 'inum=1800.FD8734,ou=scopes,o=jans', 'inum=1800.73E5B5,ou=scopes,o=jans', 'inum=1800.0DE56F,ou=scopes,o=jans', 'inum=1800.F0C44A,ou=scopes,o=jans', 'inum=1800.70AA30,ou=scopes,o=jans', 'inum=1800.632780,ou=scopes,o=jans', 'inum=1800.2646DC,ou=scopes,o=jans', 'inum=1800.54387F,ou=scopes,o=jans', 'inum=1800.16B85E,ou=scopes,o=jans', 'inum=1800.565A62,ou=scopes,o=jans', 'inum=1800.065E1A,ou=scopes,o=jans', 'inum=1800.4E54A5,ou=scopes,o=jans', 'inum=1800.50BBFE,ou=scopes,o=jans', 'inum=1800.6A2BD2,ou=scopes,o=jans', 'inum=1800.B9BE21,ou=scopes,o=jans', 'inum=1800.A8FD82,ou=scopes,o=jans', 'inum=1800.BDA282,ou=scopes,o=jans', 'inum=1800.0B7536,ou=scopes,o=jans', 'inum=1800.8100D9,ou=scopes,o=jans', 'inum=1800.987C52,ou=scopes,o=jans', 'inum=1800.DB17D4,ou=scopes,o=jans', 'inum=1800.AA15F1,ou=scopes,o=jans', 'inum=1800.5B28B1,ou=scopes,o=jans', 'inum=1800.53B808,ou=scopes,o=jans', 'inum=1800.573EBC,ou=scopes,o=jans', 'inum=2762b464-0b67-4071-8072-91cb371895a6,ou=scopes,o=jans', 'inum=378ac51e-6746-4b2c-a9dc-ca3261f998e1,ou=scopes,o=jans', 'inum=478e15ec-1871-4314-a909-3d2c84bd09f9,ou=scopes,o=jans'], 'trustedClient': False, 'persistClientAuthorizations': True, 'includeClaimsInIdToken': False, 'customAttributes': [{'name': 'displayName', 'multiValued': False, 'values': ['Jans Config Api Client'], 'displayValue': 'Jans Config Api Client', 'value': 'Jans Config Api Client'}], 'customObjectClasses': ['top'], 'rptAsJwt': False, 'accessTokenAsJwt': False, 'accessTokenSigningAlg': 'RS256', 'disabled': False, 'attributes': {'runIntrospectionScriptBeforeAccessTokenAsJwtCreationAndIncludeClaims': False, 'keepClientAuthorizationAfterExpiration': False, 'allowSpontaneousScopes': False, 'backchannelLogoutSessionRequired': False, 'parLifetime': 600, 'requirePar': False, 'jansDefaultPromptLogin': False}, 'deletable': False} \ No newline at end of file diff --git a/jans-cli-tui/radio.txt b/jans-cli-tui/radio.txt deleted file mode 100644 index fc17df55040..00000000000 --- a/jans-cli-tui/radio.txt +++ /dev/null @@ -1,20 +0,0 @@ -Name : subjectTypesSupported , value = -********** -Name : applicationType , value = web -********** -Name : accessTokenAsJwt , value = False -********** -Name : backchannelTokenDeliveryMode , value = -********** -Name : applicationType! , value = -********** -Name : subjectTypesSupported , value = -********** -Name : applicationType , value = native -********** -Name : accessTokenAsJwt , value = False -********** -Name : backchannelTokenDeliveryMode , value = -********** -Name : applicationType! , value = -********** From 29081bd4d2f7183964f4d22f72364c4dc9b9e4b5 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Wed, 17 Aug 2022 20:09:14 +0300 Subject: [PATCH 025/364] fix: jans-cli remove debug lines --- jans-cli-tui/wui_components/edit_client_dialog.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/jans-cli-tui/wui_components/edit_client_dialog.py b/jans-cli-tui/wui_components/edit_client_dialog.py index 6c063b152cf..1f7abe6dd10 100644 --- a/jans-cli-tui/wui_components/edit_client_dialog.py +++ b/jans-cli-tui/wui_components/edit_client_dialog.py @@ -282,11 +282,6 @@ def getTitledCheckBox(self, title, name, values,style=''): if type(self.data[name]) is not list : value= str(self.data[name]) - with open('./checkbox.txt', 'a') as f: - f.write("Name : {} , value = {}".format(name,value)) - f.write("\n**********\n") - - cb = CheckboxList(values=[(o,o) for o in values],default_values=value) cb.window.jans_name = name li,cd,width = self.myparent.handle_long_string(title,values,cb) @@ -299,10 +294,6 @@ def getTitledRadioButton(self, title, name, values,style=''): if type(self.data[name]) is not list : value= str(self.data[name]) - with open('./radio.txt', 'a') as f: - f.write("Name : {} , value = {}".format(name,value)) - f.write("\n**********\n") - rl = RadioList(values=[(option, option) for option in values],default=value) rl.window.jans_name = name li,rl2,width = self.myparent.handle_long_string(title,values,rl) From 787c7225200b7cc3cc906ab83ed627742375437d Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Wed, 17 Aug 2022 20:31:11 +0300 Subject: [PATCH 026/364] feat: jans-cli add logger --- jans-cli-tui/jans-cli-tui.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index 6120c4ab88c..6bee70e333d 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -3,6 +3,8 @@ """ import json import os +import logging + from shutil import get_terminal_size import time from asyncio import Future, ensure_future @@ -68,6 +70,9 @@ 'helper': ("To guide you through the fields"), } + + + home_dir = Path.home() config_dir = home_dir.joinpath('.config') config_dir.mkdir(parents=True, exist_ok=True) @@ -87,6 +92,7 @@ def do_exit(*c): class JansCliApp(Application, JansAuthServer): def __init__(self): + self.init_logger() self.app_started = False self.width, self.height = get_terminal_size() self.app = get_app() @@ -163,6 +169,17 @@ def __init__(self): self.check_jans_cli_ini() # ----------------------------------------------------------------------------- # + def init_logger(self): + self.logger = logging.getLogger('JansCli') + self.logger.setLevel(logging.DEBUG) + formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') + cur_dir = os.path.dirname(os.path.realpath(__file__)) + file_handler = logging.FileHandler(os.path.join(cur_dir, 'dev.log')) + file_handler.setLevel(logging.DEBUG) + file_handler.setFormatter(formatter) + self.logger.addHandler(file_handler) + self.logger.debug("JANS CLI Started") + def press_tab(self): self.keyboard.press(Key.tab) self.keyboard.release(Key.tab) From 2d3e2a77b21209642cf440b28e06df9c5142c152 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Thu, 18 Aug 2022 00:42:48 +0300 Subject: [PATCH 027/364] feat: jans-cli unfinished add client --- jans-cli-tui/jans-cli-tui.py | 65 +++-- jans-cli-tui/models/oauth.py | 43 ++- .../wui_components/edit_client_dialog.py | 249 ++---------------- 3 files changed, 108 insertions(+), 249 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index 6bee70e333d..130fd3c28a0 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -39,12 +39,12 @@ TextArea, CheckboxList, Shadow, + Checkbox, ) from prompt_toolkit.filters import Condition # -------------------------------------------------------------------------- # from cli import config_cli -from wui_components.edit_client_dialog import EditClientDialog from wui_components.edit_scope_dialog import EditScopeDialog from wui_components.jans_cli_dialog import JansGDialog from wui_components.jans_nav_bar import JansNavBar @@ -348,27 +348,54 @@ def handle_long_string (self,text,values,cb): return new_title , cd , width def getTitledText(self, title, name, value='', height=1, jans_help='', width=None,style=''): + title += ': ' multiline = height > 1 ta = TextArea(text=value, multiline=multiline,style="class:titledtext") ta.window.jans_name = name ta.window.jans_help = jans_help - li,cd,width = self.handle_long_string(title,[1]*height,ta) + ta.window.me = ta + li, cd, width = self.handle_long_string(title,[1]*height,ta) return VSplit([Label(text=li, width=width,style=style), cd], padding=1) - # def getTitledCheckBox(self, title, name, values,style=''): - # cb = CheckboxList(values=[(o,o) for o in values]) - # cb.window.jans_name = name - # li,cd,width = self.handle_long_string(title,values,cb) - - # return VSplit([Label(text=li, width=width,style=style,wrap_lines=False), cd]) - - # def getTitledRadioButton(self, title, name, values,style=''): - # rl = RadioList(values=[(option, option) for option in values]) - # rl.window.jans_name = name - # li,rl2,width = self.handle_long_string(title,values,rl) - - # return VSplit([Label(text=li, width=width,style=style), rl2],) + def getTitledCheckBoxList(self, title, name, values, current_values=[], jans_help='', style=''): + title += ': ' + if values and not (isinstance(values[0], tuple) or isinstance(values[0], list)): + values = [(o,o) for o in values] + cbl = CheckboxList(values=values) + cbl.current_values = current_values + cbl.window.jans_name = name + cbl.window.jans_help = jans_help + cbl.window.me = cbl + li, cd, width = self.handle_long_string(title, values, cbl) + + return VSplit([Label(text=li, width=width, style=style, wrap_lines=False), cd]) + + + def getTitledCheckBox(self, title, name, text='', checked=False, jans_help='', style=''): + title += ': ' + cb = Checkbox(text) + cb.checked = checked + cb.window.jans_name = name + cb.window.jans_help = jans_help + cb.window.me = cb + li, cd, width = self.handle_long_string(title, text, cb) + + return VSplit([Label(text=li, width=width, style=style, wrap_lines=False), cd]) + + def getTitledRadioButton(self, title, name, values, current_value=None, jans_help='', style=''): + title += ': ' + if values and not (isinstance(values[0], tuple) or isinstance(values[0], list)): + values = [(o,o) for o in values] + rl = RadioList(values=values) + if current_value: + rl.current_value = current_value + rl.window.jans_name = name + rl.window.jans_help = jans_help + rl.window.me = rl + li, rl2, width = self.handle_long_string(title,values,rl) + + return VSplit([Label(text=li, width=width,style=style), rl2],) # ----------------------------------------------------------------- # def getButton(self, text, name, jans_help, handler=None): @@ -465,14 +492,6 @@ def save_creds(self, dialog): config_cli.client_id = config_cli.config['DEFAULT']['jca_client_id'] config_cli.client_secret = config_cli.config['DEFAULT']['jca_client_secret'] - def edit_client_dialog(self, **params): - - selected_line_data = params['data'] - title = "Edit user Data (Clients)" - - dialog = EditClientDialog(self,title=title,data=selected_line_data) - self.show_jans_dialog(dialog) - def edit_scope_dialog(self, **params): dialog = EditScopeDialog(self,**params) diff --git a/jans-cli-tui/models/oauth.py b/jans-cli-tui/models/oauth.py index 70169367b07..ff280affd7f 100644 --- a/jans-cli-tui/models/oauth.py +++ b/jans-cli-tui/models/oauth.py @@ -4,7 +4,7 @@ from collections import OrderedDict import json from asyncio import Future, ensure_future - +import prompt_toolkit from prompt_toolkit.application.current import get_app from prompt_toolkit.key_binding import KeyBindings from prompt_toolkit.key_binding.bindings.focus import focus_next, focus_previous @@ -41,6 +41,7 @@ from wui_components.jans_vetrical_nav import JansVerticalNav from wui_components.jans_dialog import JansDialog from wui_components.jans_dialog_with_nav import JansDialogWithNav +from wui_components.edit_client_dialog import EditClientDialog class JansAuthServer: @@ -77,8 +78,8 @@ def oauth_prepare_containers(self): self.oauth_containers['clients'] = HSplit([ VSplit([ self.getButton(text="Get Clients", name='oauth:clients:get', jans_help="Retreive first 10 OpenID Connect clients", handler=self.oauth_get_clients), - self.getTitledText('Search: ', name='oauth:clients:search', jans_help='Press enter to perform search'), - self.getButton(text="Add Client", name='oauth:clients:add', jans_help="To add a new client press this button") + self.getTitledText('Search', name='oauth:clients:search', jans_help='Press enter to perform search'), + self.getButton(text="Add Client", name='oauth:clients:add', jans_help="To add a new client press this button", handler=self.add_client) ], padding=3, width=D(), @@ -217,3 +218,39 @@ def display_scope(self): def edit_scope(self, selected,event,size): ## enter self.edit_scope_dialog() + + def edit_client_dialog(self, **params): + + selected_line_data = params['data'] + title = "Edit user Data (Clients)" + + dialog = EditClientDialog(self, title=title, data=selected_line_data) + self.show_jans_dialog(dialog) + + def save_client(self, dialog): + data = {} + for tab in dialog.oauth_tabs: + for item in dialog.oauth_tabs[tab].children: + if hasattr(item, 'children') and hasattr(item.children[1], 'jans_name'): + key_ = item.children[1].jans_name + if isinstance(item.children[1].me, prompt_toolkit.widgets.base.TextArea): + value_ = item.children[1].me.text + elif isinstance(item.children[1].me, prompt_toolkit.widgets.base.CheckboxList): + value_ = item.children[1].me.current_values + elif isinstance(item.children[1].me, prompt_toolkit.widgets.base.RadioList): + value_ = item.children[1].me.current_value + elif isinstance(item.children[1].me, prompt_toolkit.widgets.base.Checkbox): + value_ = item.children[1].me.checked + data[key_] = value_ + + for list_key in ('redirectUris', 'scopes'): + if data[list_key]: + data[list_key] = data[list_key].splitlines() + + self.logger.debug(str(data)) + + def add_client(self): + dialog = EditClientDialog(self, title="Add Client", data={}, save_handler=self.save_client) + result = self.show_jans_dialog(dialog) + + diff --git a/jans-cli-tui/wui_components/edit_client_dialog.py b/jans-cli-tui/wui_components/edit_client_dialog.py index 1f7abe6dd10..6e46bf8f37a 100644 --- a/jans-cli-tui/wui_components/edit_client_dialog.py +++ b/jans-cli-tui/wui_components/edit_client_dialog.py @@ -46,20 +46,22 @@ class EditClientDialog(JansGDialog): - def __init__(self, parent, title, data ,buttons=...): + def __init__(self, parent, title, data, buttons=[], save_handler=None): super().__init__(parent, title, buttons) + self.save_handler = save_handler self.data = data self.prepare_tabs() - - def accept(): + def save(): + if save_handler: + self.save_handler(self) self.future.set_result(DialogResult.ACCEPT) - + def cancel(): self.future.set_result(DialogResult.CANCEL) self.side_NavBar = JansSideNavBar(myparent=self.myparent, - entries=list(self.oauth_tabs['clients'].keys()), + entries=list(self.oauth_tabs.keys()), selection_changed=(self.client_dialog_nav_selection_changed) , select=0, entries_color='#2600ff') @@ -67,9 +69,9 @@ def cancel(): self.dialog = JansDialogWithNav( title=title, navbar=DynamicContainer(lambda:self.side_NavBar), - content=DynamicContainer(lambda: self.oauth_tabs['clients'][self.oauth_dialog_nav]), + content=DynamicContainer(lambda: self.oauth_tabs[self.oauth_dialog_nav]), button_functions=[ - (accept, "Save"), + (save, "Save"), (cancel, "Cancel") ], height=self.myparent.dialog_height, @@ -78,228 +80,29 @@ def cancel(): def prepare_tabs(self): self.oauth_tabs = {} - self.oauth_tabs['clients'] = OrderedDict() - - self.oauth_tabs['clients']['Basic'] = HSplit([ - VSplit([ - self.getTitledText(title ="Client_ID", name='inum',style='green',), - Window(width=1, char=" ",), - self.getTitledCheckBox("Active:", name='disabled', values=['active'],style='green'), - ]) , - self.getTitledText("Client Name", name='displayName',style='green'), - self.getTitledText("Client Secret", name='clientSecret',style='green'), - self.getTitledText("Description", name='description',style='green'), + self.oauth_tabs = OrderedDict() + + self.oauth_tabs['Basic'] = HSplit([ + self.myparent.getTitledText(title ="Client_ID", name='inum', value=self.data.get('inum',''), style='green'), + self.myparent.getTitledCheckBox("Active", name='disabled', checked= not self.data.get('disabled'), style='green'), + self.myparent.getTitledText("Client Name", name='displayName', value=self.data.get('displayName',''), style='green'), + self.myparent.getTitledText("Client Secret", name='clientSecret', value=self.data.get('clientSecret',''), style='green'), + self.myparent.getTitledText("Description", name='description', value=self.data.get('description',''), style='green'), Label(text="dropdown1",style='blue'), - self.getTitledRadioButton("Id_token Subject Type:", name='subjectTypesSupported', values=['Public','Pairwise'],style='green'), - self.getTitledCheckBox("Grant:", name='grantTypes', values=['Authorizatuin Code', 'Refresh Token', 'UMA Ticket','Client Credential','Password','Implicit'],style='green'), - self.getTitledCheckBox("Response Types:", name='responseTypes', values=['code', 'token', 'id_token'],style='green'), - self.getTitledCheckBox("Supress Authorization:", name='dynamicRegistrationPersistClientAuthorizations', values=['True'],style='green'), - self.getTitledRadioButton("Application Type:", name='applicationType', values=[ 'native','web'],style='green'), - self.getTitledText("Redirect Uris", name='redirectUris', height=3,style='green'), - self.getTitledText("Redirect Regex", name='redirectregex',style='green'), - self.getTitledText("Scopes", name='scopes', height=3,style='green'), + self.myparent.getTitledRadioButton("Subject Type", name='subjectType', values=[('public', 'Public'),('pairwise', 'Pairwise')], current_value=self.data.get('subjectType'), style='green'), + self.myparent.getTitledCheckBoxList("Grant", name='grantTypes', values=[('authorization_code', 'Authorizatuin Code'), ('refresh_token', 'Refresh Token'), ('urn:ietf:params:oauth:grant-type:uma-ticket', 'UMA Ticket'), ('client_credentials', 'Client Credentials'), ('password', 'Password'), ('implicit', 'Implicit')], current_values=self.data.get('grantTypes', []), style='green'), + self.myparent.getTitledCheckBoxList("Response Types", name='responseTypes', values=['code', 'token', 'id_token'], current_values=self.data.get('responseTypes', []), style='green'), + self.myparent.getTitledCheckBox("Supress Authorization", name='dynamicRegistrationPersistClientAuthorizations', checked=self.data.get('dynamicRegistrationPersistClientAuthorizations'), style='green'), + self.myparent.getTitledRadioButton("Application Type", name='applicationType', values=['native','web'], current_value=self.data.get('applicationType'), style='green'), + self.myparent.getTitledText("Redirect Uris", name='redirectUris', value='\n'.join(self.data.get('redirectUris', [])), height=3, style='green'), + #self.myparent.getTitledText("Redirect Regex", name='redirectregex', value=self.data.get('redirectUris', ''), style='green'), #name not identified + self.myparent.getTitledText("Scopes", name='scopes', value='\n'.join(self.data.get('scopes', [])), height=3, style='green'), ],width=D(), ) - self.oauth_tabs['clients']['Tokens'] = HSplit([ - self.getTitledRadioButton("Access Token Type", name='accessTokenAsJwt', values=['JWT','Reference'],style='green'), - self.getTitledCheckBox("Incliude Claims in id_token", name='includeClaimsInIdToken', values=['True'],style='green'), - self.getTitledCheckBox("Run introspection script before JWT access token creation", name='runIntrospectionScriptBeforeAccessTokenAsJwtCreationAndIncludeClaims', values=['True'],style='green'), - self.getTitledText("Token binding confirmation method for id_token", name='idTokenTokenBindingCnf',style='green'), - self.getTitledText("Access token additional audiences", name='additionalAudience',style='green'), - VSplit([ - Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3) - ]), - self.getTitledText("Access token lifetime", name='accessTokenLifetime',style='green'), - self.getTitledText("Refresh token lifetime", name='refreshTokenLifetime',style='green'), - self.getTitledText("Defult max authn age", name='defaultMaxAge',style='green'), - - ],width=D()) - - self.oauth_tabs['clients']['Logout'] = HSplit([ - - self.getTitledText("Front channel logout URI", name='frontChannelLogoutUri',style='green'), - self.getTitledText("Post logout redirect URI", name='postLogoutRedirectUris',style='green'), - self.getTitledText("Back channel logout URI", name='backchannelLogoutUri',style='green'), - self.getTitledCheckBox("Back channel logout session required", name='backchannelLogoutSessionRequired', values=['True'],style='green'), - self.getTitledCheckBox("Front channel logout session required", name='frontChannelLogoutSessionRequired', values=['True'],style='green'), - - ],width=D() - ) - - self.oauth_tabs['clients']['Software Info'] = HSplit([ - self.getTitledText("Client URI", name='clientUri',style='green'), - self.getTitledText("Policy URI", name='policyUri',style='green'), - self.getTitledText("Logo URI", name='logoUri',style='green'), - self.getTitledText("Term of service URI", name='term_of_service_URI',style='green'), - self.getTitledText("Contacts", name='contacts',style='green'), - VSplit([ - Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3,) - ]), - self.getTitledText("Authorized JS origins", name='authorizedOrigins',style='green'), - VSplit([ - Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3) - ]), - self.getTitledText("Software id", name='softwareId',style='green'), - self.getTitledText("Software version", name='softwareVersion',style='green'), - self.getTitledText("Software statement", name='softwareStatement',style='green'), - - ],width=D()) - - self.oauth_tabs['clients']['CIBA/PAR/UMA'] = HSplit([ - Label(text="CIBA",style='bold'), - self.getTitledRadioButton("Token delivery method", name='backchannelTokenDeliveryMode', values=['poll','push', 'ping'],style='green'), - self.getTitledText("Client notification endpoint", name='backchannelClientNotificationEndpoint',style='green'), - self.getTitledCheckBox("Require user code param", name='backchannelUserCodeParameterSupported', values=['True'],style='green'), - - Label(text="PAR",style='bold'), - self.getTitledText("Request lifetime", name='request!',style='green'), - self.getTitledCheckBox("Request PAR", name='sessionIdRequestParameterEnabled', values=['True'],style='green'), - - Label("UMA",style='bold'), - self.getTitledRadioButton("PRT token type", name='applicationType!', values=['JWT', 'Reference'],style='green'), - self.getTitledText("Claims redirect URI", name='claims!',style='green'), - - # self.myparent.getButton(text="dropdown1", name='oauth:scopes:dropdown1', jans_help="dropdown1",handler=self.myparent.testdropdown), - # self.myparent.getButton(text="dropdown2", name='oauth:scopes:dropdown2', jans_help="dropdown2",handler=self.myparent.testdropdown), - - Label(text="dropdown1",style='blue'), ## TODO with Jans VerticalNav - Label(text="dropdown2",style='blue'), ## TODO with Jans VerticalNav - Label(text="tabel",style='blue'), ## TODO with Jans VerticalNav - - ] - ) - - self.oauth_tabs['clients']['Encryption/Signing'] = HSplit([ - self.getTitledText("Client JWKS URI", name='jwksUri',style='green'), - self.getTitledText("Client JWKS", name='jwks',style='green'), - VSplit([ - Label(text="id_token"), - Label(text="a, b, c",style='red'), - ]), - VSplit([ - Label(text="Access token"), - Label(text="a",style='red'), - ]), - VSplit([ - Label(text="Userinfo"), - Label(text="a, b, c",style='red'), - ]), - VSplit([ - Label(text="JARM"), - Label(text="a, b, c",style='red'), - ]), - VSplit([ - Label(text="Request Object"), - Label(text="a, b, c",style='red'), - ]), - - ] - ) - - self.oauth_tabs['clients']['Advanced Client Properties'] = HSplit([ - - self.getTitledCheckBox("Default Prompt=login", name='defaultPromptLogin', values=['True'],style='green'), - VSplit([ - self.getTitledCheckBox("Persist Authorizations", name='persistClientAuthorizations', values=['True'],style='green'), - self.getTitledCheckBox("Keep expired?", name='Supress', values=['True'],style='green'), - ]) , - self.getTitledCheckBox("Allow spontaneos scopes", name='allowSpontaneousScopes', values=['True'],style='green'), - - self.getTitledText("spontaneos scopes validation regex", name='spontaneousScopes',style='green'), - VSplit([ - Label(text="Spontaneous Scopes",style='green'), - Button("view current", handler=self.myparent.show_again,left_symbol='',right_symbol='',) - - ]) , - self.getTitledText("Initial Login URI", name='initiateLoginUri',style='green'), - - VSplit([ - self.getTitledText("Request URIs", name='requestUris',style='green'), - Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3,) - ]) , - - Label(text="Dropdown 3",style='blue'), - - VSplit([ - self.getTitledText("Allowed ACRs", name='clientSecret',style='green'), - Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3,) - ]) , - self.getTitledText("TLS Subject DN", name='clientSecret',style='green'), - - VSplit([ - self.getTitledCheckBox("Client Experiation Date", name='id_token_claims', values=['True'],style='green'), - Label(text="Pick Date",style='blue'), - ]) , - - - - ],width=D() - ) - - self.oauth_tabs['clients']['Client Scripts'] = HSplit([ - Label(text="Dropdown 4",style='blue'), - Label(text="Dropdown 5",style='blue'), - Label(text="Dropdown 6",style='blue'), - Label(text="Dropdown 7",style='blue'), - Label(text="Dropdown 8",style='blue'), - ] - ) - - self.oauth_tabs['clients']['Save'] = HSplit([ - Button("Save", handler=self.myparent.show_again,left_symbol='(',right_symbol=')',) - - ],width=D() - ) - - self.oauth_dialog_nav = list(self.oauth_tabs['clients'].keys())[0] - - def getTitledText(self, title, name, value='', height=1, jans_help='', width=None,style='',data=[]): - ### Custom getTitledText (i didnt want to mess with the old one) - ### search for getTitledText widget name in `data` - ### data is Dict - - if name in self.data.keys() : - if type(self.data[name]) is not list : - value= str(self.data[name]) - else : - value= str('\n'.join(self.data[name])) -# - multiline = height > 1 - ta = TextArea(text=value, multiline=multiline,style="class:titledtext") - ta.window.jans_name = name - ta.window.jans_help = jans_help - li,cd,width = self.myparent.handle_long_string(title,[1]*height,ta) - - return VSplit([Label(text=li, width=width,style=style), cd], padding=1) - - - def getTitledCheckBox(self, title, name, values,style=''): - - value = '' - if name in self.data.keys() : - if type(self.data[name]) is not list : - value= str(self.data[name]) - - cb = CheckboxList(values=[(o,o) for o in values],default_values=value) - cb.window.jans_name = name - li,cd,width = self.myparent.handle_long_string(title,values,cb) - - return VSplit([Label(text=li, width=width,style=style,wrap_lines=False), cd]) - - def getTitledRadioButton(self, title, name, values,style=''): - value = '' - if name in self.data.keys() : - if type(self.data[name]) is not list : - value= str(self.data[name]) - - rl = RadioList(values=[(option, option) for option in values],default=value) - rl.window.jans_name = name - li,rl2,width = self.myparent.handle_long_string(title,values,rl) - - return VSplit([Label(text=li, width=width,style=style), rl2],) + self.oauth_dialog_nav = list(self.oauth_tabs.keys())[0] def client_dialog_nav_selection_changed(self, selection): From b52ead1dde690d92fda6928c0103235900e29b95 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Thu, 18 Aug 2022 11:53:18 +0300 Subject: [PATCH 028/364] fix: jans-cli rename var names --- jans-cli-tui/models/oauth.py | 6 +++-- .../wui_components/edit_client_dialog.py | 24 ++++++++++--------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/jans-cli-tui/models/oauth.py b/jans-cli-tui/models/oauth.py index ff280affd7f..2a4f87cdf94 100644 --- a/jans-cli-tui/models/oauth.py +++ b/jans-cli-tui/models/oauth.py @@ -229,8 +229,8 @@ def edit_client_dialog(self, **params): def save_client(self, dialog): data = {} - for tab in dialog.oauth_tabs: - for item in dialog.oauth_tabs[tab].children: + for tab in dialog.tabs: + for item in dialog.tabs[tab].children: if hasattr(item, 'children') and hasattr(item.children[1], 'jans_name'): key_ = item.children[1].jans_name if isinstance(item.children[1].me, prompt_toolkit.widgets.base.TextArea): @@ -249,6 +249,8 @@ def save_client(self, dialog): self.logger.debug(str(data)) + return False + def add_client(self): dialog = EditClientDialog(self, title="Add Client", data={}, save_handler=self.save_client) result = self.show_jans_dialog(dialog) diff --git a/jans-cli-tui/wui_components/edit_client_dialog.py b/jans-cli-tui/wui_components/edit_client_dialog.py index 6e46bf8f37a..4e3a30ee37d 100644 --- a/jans-cli-tui/wui_components/edit_client_dialog.py +++ b/jans-cli-tui/wui_components/edit_client_dialog.py @@ -53,23 +53,25 @@ def __init__(self, parent, title, data, buttons=[], save_handler=None): self.prepare_tabs() def save(): + close_me = True if save_handler: - self.save_handler(self) - self.future.set_result(DialogResult.ACCEPT) + close_me = self.save_handler(self) + if close_me: + self.future.set_result(DialogResult.ACCEPT) def cancel(): self.future.set_result(DialogResult.CANCEL) - self.side_NavBar = JansSideNavBar(myparent=self.myparent, - entries=list(self.oauth_tabs.keys()), + self.side_nav_bar = JansSideNavBar(myparent=self.myparent, + entries=list(self.tabs.keys()), selection_changed=(self.client_dialog_nav_selection_changed) , select=0, entries_color='#2600ff') self.dialog = JansDialogWithNav( title=title, - navbar=DynamicContainer(lambda:self.side_NavBar), - content=DynamicContainer(lambda: self.oauth_tabs[self.oauth_dialog_nav]), + navbar=DynamicContainer(lambda:self.side_nav_bar), + content=DynamicContainer(lambda: self.tabs[self.left_nav]), button_functions=[ (save, "Save"), (cancel, "Cancel") @@ -79,10 +81,10 @@ def cancel(): ) def prepare_tabs(self): - self.oauth_tabs = {} - self.oauth_tabs = OrderedDict() - self.oauth_tabs['Basic'] = HSplit([ + self.tabs = OrderedDict() + + self.tabs['Basic'] = HSplit([ self.myparent.getTitledText(title ="Client_ID", name='inum', value=self.data.get('inum',''), style='green'), self.myparent.getTitledCheckBox("Active", name='disabled', checked= not self.data.get('disabled'), style='green'), self.myparent.getTitledText("Client Name", name='displayName', value=self.data.get('displayName',''), style='green'), @@ -102,11 +104,11 @@ def prepare_tabs(self): ) - self.oauth_dialog_nav = list(self.oauth_tabs.keys())[0] + self.left_nav = list(self.tabs.keys())[0] def client_dialog_nav_selection_changed(self, selection): - self.oauth_dialog_nav = selection + self.left_nav = selection def __pt_container__(self): return self.dialog From 577eb22ff0c9516194edd758d02de8b28240cebf Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Thu, 18 Aug 2022 11:55:21 +0300 Subject: [PATCH 029/364] fix: jans-cli remove tabulate library --- jans-cli-tui/cli/config_cli.py | 1 - jans-cli-tui/cli/pylib/__init__.py | 0 jans-cli-tui/cli/pylib/tabulate/LICENSE | 20 - jans-cli-tui/cli/pylib/tabulate/README | 1 - jans-cli-tui/cli/pylib/tabulate/README.md | 747 -------- jans-cli-tui/cli/pylib/tabulate/__init__.py | 0 jans-cli-tui/cli/pylib/tabulate/tabulate.py | 1792 ------------------- 7 files changed, 2561 deletions(-) delete mode 100644 jans-cli-tui/cli/pylib/__init__.py delete mode 100644 jans-cli-tui/cli/pylib/tabulate/LICENSE delete mode 100644 jans-cli-tui/cli/pylib/tabulate/README delete mode 100644 jans-cli-tui/cli/pylib/tabulate/README.md delete mode 100644 jans-cli-tui/cli/pylib/tabulate/__init__.py delete mode 100644 jans-cli-tui/cli/pylib/tabulate/tabulate.py diff --git a/jans-cli-tui/cli/config_cli.py b/jans-cli-tui/cli/config_cli.py index 78d8406fb99..bfe759f3190 100755 --- a/jans-cli-tui/cli/config_cli.py +++ b/jans-cli-tui/cli/config_cli.py @@ -36,7 +36,6 @@ cur_dir = os.path.dirname(os.path.realpath(__file__)) sys.path.append(cur_dir) -from pylib.tabulate.tabulate import tabulate try: import jwt except ModuleNotFoundError: diff --git a/jans-cli-tui/cli/pylib/__init__.py b/jans-cli-tui/cli/pylib/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/jans-cli-tui/cli/pylib/tabulate/LICENSE b/jans-cli-tui/cli/pylib/tabulate/LICENSE deleted file mode 100644 index 81241eca637..00000000000 --- a/jans-cli-tui/cli/pylib/tabulate/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2011-2020 Sergey Astanin and contributors - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/jans-cli-tui/cli/pylib/tabulate/README b/jans-cli-tui/cli/pylib/tabulate/README deleted file mode 100644 index 42061c01a1c..00000000000 --- a/jans-cli-tui/cli/pylib/tabulate/README +++ /dev/null @@ -1 +0,0 @@ -README.md \ No newline at end of file diff --git a/jans-cli-tui/cli/pylib/tabulate/README.md b/jans-cli-tui/cli/pylib/tabulate/README.md deleted file mode 100644 index ce06dadc834..00000000000 --- a/jans-cli-tui/cli/pylib/tabulate/README.md +++ /dev/null @@ -1,747 +0,0 @@ -python-tabulate -=============== - -Pretty-print tabular data in Python, a library and a command-line -utility. - -The main use cases of the library are: - -- printing small tables without hassle: just one function call, - formatting is guided by the data itself -- authoring tabular data for lightweight plain-text markup: multiple - output formats suitable for further editing or transformation -- readable presentation of mixed textual and numeric data: smart - column alignment, configurable number formatting, alignment by a - decimal point - -Installation ------------- - -To install the Python library and the command line utility, run: - - pip install tabulate - -The command line utility will be installed as `tabulate` to `bin` on -Linux (e.g. `/usr/bin`); or as `tabulate.exe` to `Scripts` in your -Python installation on Windows (e.g. -`C:\Python27\Scripts\tabulate.exe`). - -You may consider installing the library only for the current user: - - pip install tabulate --user - -In this case the command line utility will be installed to -`~/.local/bin/tabulate` on Linux and to -`%APPDATA%\Python\Scripts\tabulate.exe` on Windows. - -To install just the library on Unix-like operating systems: - - TABULATE_INSTALL=lib-only pip install tabulate - -On Windows: - - set TABULATE_INSTALL=lib-only - pip install tabulate - -Build status ------------- - -[![Build status](https://circleci.com/gh/astanin/python-tabulate.svg?style=svg)](https://circleci.com/gh/astanin/python-tabulate/tree/master) [![Build status](https://ci.appveyor.com/api/projects/status/8745yksvvol7h3d7/branch/master?svg=true)](https://ci.appveyor.com/project/astanin/python-tabulate/branch/master) - -Library usage -------------- - -The module provides just one function, `tabulate`, which takes a list of -lists or another tabular data type as the first argument, and outputs a -nicely formatted plain-text table: - - >>> from tabulate import tabulate - - >>> table = [["Sun",696000,1989100000],["Earth",6371,5973.6], - ... ["Moon",1737,73.5],["Mars",3390,641.85]] - >>> print(tabulate(table)) - ----- ------ ------------- - Sun 696000 1.9891e+09 - Earth 6371 5973.6 - Moon 1737 73.5 - Mars 3390 641.85 - ----- ------ ------------- - -The following tabular data types are supported: - -- list of lists or another iterable of iterables -- list or another iterable of dicts (keys as columns) -- dict of iterables (keys as columns) -- two-dimensional NumPy array -- NumPy record arrays (names as columns) -- pandas.DataFrame - -Examples in this file use Python2. Tabulate supports Python3 too. - -### Headers - -The second optional argument named `headers` defines a list of column -headers to be used: - - >>> print(tabulate(table, headers=["Planet","R (km)", "mass (x 10^29 kg)"])) - Planet R (km) mass (x 10^29 kg) - -------- -------- ------------------- - Sun 696000 1.9891e+09 - Earth 6371 5973.6 - Moon 1737 73.5 - Mars 3390 641.85 - -If `headers="firstrow"`, then the first row of data is used: - - >>> print(tabulate([["Name","Age"],["Alice",24],["Bob",19]], - ... headers="firstrow")) - Name Age - ------ ----- - Alice 24 - Bob 19 - -If `headers="keys"`, then the keys of a dictionary/dataframe, or column -indices are used. It also works for NumPy record arrays and lists of -dictionaries or named tuples: - - >>> print(tabulate({"Name": ["Alice", "Bob"], - ... "Age": [24, 19]}, headers="keys")) - Age Name - ----- ------ - 24 Alice - 19 Bob - -### Row Indices - -By default, only pandas.DataFrame tables have an additional column -called row index. To add a similar column to any other type of table, -pass `showindex="always"` or `showindex=True` argument to `tabulate()`. -To suppress row indices for all types of data, pass `showindex="never"` -or `showindex=False`. To add a custom row index column, pass -`showindex=rowIDs`, where `rowIDs` is some iterable: - - >>> print(tabulate([["F",24],["M",19]], showindex="always")) - - - -- - 0 F 24 - 1 M 19 - - - -- - -### Table format - -There is more than one way to format a table in plain text. The third -optional argument named `tablefmt` defines how the table is formatted. - -Supported table formats are: - -- "plain" -- "simple" -- "github" -- "grid" -- "fancy\_grid" -- "pipe" -- "orgtbl" -- "jira" -- "presto" -- "pretty" -- "psql" -- "rst" -- "mediawiki" -- "moinmoin" -- "youtrack" -- "html" -- "unsafehtml" -- "latex" -- "latex\_raw" -- "latex\_booktabs" -- "textile" - -`plain` tables do not use any pseudo-graphics to draw lines: - - >>> table = [["spam",42],["eggs",451],["bacon",0]] - >>> headers = ["item", "qty"] - >>> print(tabulate(table, headers, tablefmt="plain")) - item qty - spam 42 - eggs 451 - bacon 0 - -`simple` is the default format (the default may change in future -versions). It corresponds to `simple_tables` in [Pandoc Markdown -extensions](http://johnmacfarlane.net/pandoc/README.html#tables): - - >>> print(tabulate(table, headers, tablefmt="simple")) - item qty - ------ ----- - spam 42 - eggs 451 - bacon 0 - -`github` follows the conventions of Github flavored Markdown. It -corresponds to the `pipe` format without alignment colons: - - >>> print(tabulate(table, headers, tablefmt="github")) - | item | qty | - |--------|-------| - | spam | 42 | - | eggs | 451 | - | bacon | 0 | - -`grid` is like tables formatted by Emacs' -[table.el](http://table.sourceforge.net/) package. It corresponds to -`grid_tables` in Pandoc Markdown extensions: - - >>> print(tabulate(table, headers, tablefmt="grid")) - +--------+-------+ - | item | qty | - +========+=======+ - | spam | 42 | - +--------+-------+ - | eggs | 451 | - +--------+-------+ - | bacon | 0 | - +--------+-------+ - -`fancy_grid` draws a grid using box-drawing characters: - - >>> print(tabulate(table, headers, tablefmt="fancy_grid")) - ╒════════╤═══════╕ - │ item │ qty │ - ╞════════╪═══════╡ - │ spam │ 42 │ - ├────────┼───────┤ - │ eggs │ 451 │ - ├────────┼───────┤ - │ bacon │ 0 │ - ╘════════╧═══════╛ - -`presto` is like tables formatted by Presto cli: - - >>> print(tabulate(table, headers, tablefmt="presto")) - item | qty - --------+------- - spam | 42 - eggs | 451 - bacon | 0 - -`pretty` attempts to be close to the format emitted by the PrettyTables -library: - - >>> print(tabulate(table, headers, tablefmt="pretty")) - +-------+-----+ - | item | qty | - +-------+-----+ - | spam | 42 | - | eggs | 451 | - | bacon | 0 | - +-------+-----+ - -`psql` is like tables formatted by Postgres' psql cli: - - >>> print(tabulate(table, headers, tablefmt="psql")) - +--------+-------+ - | item | qty | - |--------+-------| - | spam | 42 | - | eggs | 451 | - | bacon | 0 | - +--------+-------+ - -`pipe` follows the conventions of [PHP Markdown -Extra](http://michelf.ca/projects/php-markdown/extra/#table) extension. -It corresponds to `pipe_tables` in Pandoc. This format uses colons to -indicate column alignment: - - >>> print(tabulate(table, headers, tablefmt="pipe")) - | item | qty | - |:-------|------:| - | spam | 42 | - | eggs | 451 | - | bacon | 0 | - -`orgtbl` follows the conventions of Emacs -[org-mode](http://orgmode.org/manual/Tables.html), and is editable also -in the minor orgtbl-mode. Hence its name: - - >>> print(tabulate(table, headers, tablefmt="orgtbl")) - | item | qty | - |--------+-------| - | spam | 42 | - | eggs | 451 | - | bacon | 0 | - -`jira` follows the conventions of Atlassian Jira markup language: - - >>> print(tabulate(table, headers, tablefmt="jira")) - || item || qty || - | spam | 42 | - | eggs | 451 | - | bacon | 0 | - -`rst` formats data like a simple table of the -[reStructuredText](http://docutils.sourceforge.net/docs/user/rst/quickref.html#tables) -format: - - >>> print(tabulate(table, headers, tablefmt="rst")) - ====== ===== - item qty - ====== ===== - spam 42 - eggs 451 - bacon 0 - ====== ===== - -`mediawiki` format produces a table markup used in -[Wikipedia](http://www.mediawiki.org/wiki/Help:Tables) and on other -MediaWiki-based sites: - - >>> print(tabulate(table, headers, tablefmt="mediawiki")) - {| class="wikitable" style="text-align: left;" - |+ - |- - ! item !! align="right"| qty - |- - | spam || align="right"| 42 - |- - | eggs || align="right"| 451 - |- - | bacon || align="right"| 0 - |} - -`moinmoin` format produces a table markup used in -[MoinMoin](https://moinmo.in/) wikis: - - >>> print(tabulate(table, headers, tablefmt="moinmoin")) - || ''' item ''' || ''' quantity ''' || - || spam || 41.999 || - || eggs || 451 || - || bacon || || - -`youtrack` format produces a table markup used in Youtrack tickets: - - >>> print(tabulate(table, headers, tablefmt="youtrack")) - || item || quantity || - | spam | 41.999 | - | eggs | 451 | - | bacon | | - -`textile` format produces a table markup used in -[Textile](http://redcloth.org/hobix.com/textile/) format: - - >>> print(tabulate(table, headers, tablefmt="textile")) - |_. item |_. qty | - |<. spam |>. 42 | - |<. eggs |>. 451 | - |<. bacon |>. 0 | - -`html` produces standard HTML markup as an html.escape'd str -with a ._repr_html_ method so that Jupyter Lab and Notebook display the HTML -and a .str property so that the raw HTML remains accessible. -`unsafehtml` table format can be used if an unescaped HTML is required: - - >>> print(tabulate(table, headers, tablefmt="html")) - - - - - - - -
item qty
spam 42
eggs 451
bacon 0
- -`latex` format creates a `tabular` environment for LaTeX markup, -replacing special characters like `_` or `\` to their LaTeX -correspondents: - - >>> print(tabulate(table, headers, tablefmt="latex")) - \begin{tabular}{lr} - \hline - item & qty \\ - \hline - spam & 42 \\ - eggs & 451 \\ - bacon & 0 \\ - \hline - \end{tabular} - -`latex_raw` behaves like `latex` but does not escape LaTeX commands and -special characters. - -`latex_booktabs` creates a `tabular` environment for LaTeX markup using -spacing and style from the `booktabs` package. - -### Column alignment - -`tabulate` is smart about column alignment. It detects columns which -contain only numbers, and aligns them by a decimal point (or flushes -them to the right if they appear to be integers). Text columns are -flushed to the left. - -You can override the default alignment with `numalign` and `stralign` -named arguments. Possible column alignments are: `right`, `center`, -`left`, `decimal` (only for numbers), and `None` (to disable alignment). - -Aligning by a decimal point works best when you need to compare numbers -at a glance: - - >>> print(tabulate([[1.2345],[123.45],[12.345],[12345],[1234.5]])) - ---------- - 1.2345 - 123.45 - 12.345 - 12345 - 1234.5 - ---------- - -Compare this with a more common right alignment: - - >>> print(tabulate([[1.2345],[123.45],[12.345],[12345],[1234.5]], numalign="right")) - ------ - 1.2345 - 123.45 - 12.345 - 12345 - 1234.5 - ------ - -For `tabulate`, anything which can be parsed as a number is a number. -Even numbers represented as strings are aligned properly. This feature -comes in handy when reading a mixed table of text and numbers from a -file: - - >>> import csv ; from StringIO import StringIO - >>> table = list(csv.reader(StringIO("spam, 42\neggs, 451\n"))) - >>> table - [['spam', ' 42'], ['eggs', ' 451']] - >>> print(tabulate(table)) - ---- ---- - spam 42 - eggs 451 - ---- ---- - - -To disable this feature use `disable_numparse=True`. - - >>> print(tabulate.tabulate([["Ver1", "18.0"], ["Ver2","19.2"]], tablefmt="simple", disable_numparse=True)) - ---- ---- - Ver1 18.0 - Ver2 19.2 - ---- ---- - - -### Custom column alignment - -`tabulate` allows a custom column alignment to override the above. The -`colalign` argument can be a list or a tuple of `stralign` named -arguments. Possible column alignments are: `right`, `center`, `left`, -`decimal` (only for numbers), and `None` (to disable alignment). -Omitting an alignment uses the default. For example: - - >>> print(tabulate([["one", "two"], ["three", "four"]], colalign=("right",)) - ----- ---- - one two - three four - ----- ---- - -### Number formatting - -`tabulate` allows to define custom number formatting applied to all -columns of decimal numbers. Use `floatfmt` named argument: - - >>> print(tabulate([["pi",3.141593],["e",2.718282]], floatfmt=".4f")) - -- ------ - pi 3.1416 - e 2.7183 - -- ------ - -`floatfmt` argument can be a list or a tuple of format strings, one per -column, in which case every column may have different number formatting: - - >>> print(tabulate([[0.12345, 0.12345, 0.12345]], floatfmt=(".1f", ".3f"))) - --- ----- ------- - 0.1 0.123 0.12345 - --- ----- ------- - -### Text formatting - -By default, `tabulate` removes leading and trailing whitespace from text -columns. To disable whitespace removal, set the global module-level flag -`PRESERVE_WHITESPACE`: - - import tabulate - tabulate.PRESERVE_WHITESPACE = True - -### Wide (fullwidth CJK) symbols - -To properly align tables which contain wide characters (typically -fullwidth glyphs from Chinese, Japanese or Korean languages), the user -should install `wcwidth` library. To install it together with -`tabulate`: - - pip install tabulate[widechars] - -Wide character support is enabled automatically if `wcwidth` library is -already installed. To disable wide characters support without -uninstalling `wcwidth`, set the global module-level flag -`WIDE_CHARS_MODE`: - - import tabulate - tabulate.WIDE_CHARS_MODE = False - -### Multiline cells - -Most table formats support multiline cell text (text containing newline -characters). The newline characters are honored as line break -characters. - -Multiline cells are supported for data rows and for header rows. - -Further automatic line breaks are not inserted. Of course, some output -formats such as latex or html handle automatic formatting of the cell -content on their own, but for those that don't, the newline characters -in the input cell text are the only means to break a line in cell text. - -Note that some output formats (e.g. simple, or plain) do not represent -row delimiters, so that the representation of multiline cells in such -formats may be ambiguous to the reader. - -The following examples of formatted output use the following table with -a multiline cell, and headers with a multiline cell: - - >>> table = [["eggs",451],["more\nspam",42]] - >>> headers = ["item\nname", "qty"] - -`plain` tables: - - >>> print(tabulate(table, headers, tablefmt="plain")) - item qty - name - eggs 451 - more 42 - spam - -`simple` tables: - - >>> print(tabulate(table, headers, tablefmt="simple")) - item qty - name - ------ ----- - eggs 451 - more 42 - spam - -`grid` tables: - - >>> print(tabulate(table, headers, tablefmt="grid")) - +--------+-------+ - | item | qty | - | name | | - +========+=======+ - | eggs | 451 | - +--------+-------+ - | more | 42 | - | spam | | - +--------+-------+ - -`fancy_grid` tables: - - >>> print(tabulate(table, headers, tablefmt="fancy_grid")) - ╒════════╤═══════╕ - │ item │ qty │ - │ name │ │ - ╞════════╪═══════╡ - │ eggs │ 451 │ - ├────────┼───────┤ - │ more │ 42 │ - │ spam │ │ - ╘════════╧═══════╛ - -`pipe` tables: - - >>> print(tabulate(table, headers, tablefmt="pipe")) - | item | qty | - | name | | - |:-------|------:| - | eggs | 451 | - | more | 42 | - | spam | | - -`orgtbl` tables: - - >>> print(tabulate(table, headers, tablefmt="orgtbl")) - | item | qty | - | name | | - |--------+-------| - | eggs | 451 | - | more | 42 | - | spam | | - -`jira` tables: - - >>> print(tabulate(table, headers, tablefmt="jira")) - | item | qty | - | name | | - |:-------|------:| - | eggs | 451 | - | more | 42 | - | spam | | - -`presto` tables: - - >>> print(tabulate(table, headers, tablefmt="presto")) - item | qty - name | - --------+------- - eggs | 451 - more | 42 - spam | - -`pretty` tables: - - >>> print(tabulate(table, headers, tablefmt="pretty")) - +------+-----+ - | item | qty | - | name | | - +------+-----+ - | eggs | 451 | - | more | 42 | - | spam | | - +------+-----+ - -`psql` tables: - - >>> print(tabulate(table, headers, tablefmt="psql")) - +--------+-------+ - | item | qty | - | name | | - |--------+-------| - | eggs | 451 | - | more | 42 | - | spam | | - +--------+-------+ - -`rst` tables: - - >>> print(tabulate(table, headers, tablefmt="rst")) - ====== ===== - item qty - name - ====== ===== - eggs 451 - more 42 - spam - ====== ===== - -Multiline cells are not well supported for the other table formats. - -Usage of the command line utility ---------------------------------- - - Usage: tabulate [options] [FILE ...] - - FILE a filename of the file with tabular data; - if "-" or missing, read data from stdin. - - Options: - - -h, --help show this message - -1, --header use the first row of data as a table header - -o FILE, --output FILE print table to FILE (default: stdout) - -s REGEXP, --sep REGEXP use a custom column separator (default: whitespace) - -F FPFMT, --float FPFMT floating point number format (default: g) - -f FMT, --format FMT set output table format; supported formats: - plain, simple, github, grid, fancy_grid, pipe, - orgtbl, rst, mediawiki, html, latex, latex_raw, - latex_booktabs, tsv - (default: simple) - -Performance considerations --------------------------- - -Such features as decimal point alignment and trying to parse everything -as a number imply that `tabulate`: - -- has to "guess" how to print a particular tabular data type -- needs to keep the entire table in-memory -- has to "transpose" the table twice -- does much more work than it may appear - -It may not be suitable for serializing really big tables (but who's -going to do that, anyway?) or printing tables in performance sensitive -applications. `tabulate` is about two orders of magnitude slower than -simply joining lists of values with a tab, coma or other separator. - -In the same time `tabulate` is comparable to other table -pretty-printers. Given a 10x10 table (a list of lists) of mixed text and -numeric data, `tabulate` appears to be slower than `asciitable`, and -faster than `PrettyTable` and `texttable` The following mini-benchmark -was run in Python 3.8.1 in Windows 10 x64: - - =========================== ========== =========== - Table formatter time, μs rel. time - =========================== ========== =========== - csv to StringIO 12.4 1.0 - join with tabs and newlines 15.7 1.3 - asciitable (0.8.0) 208.3 16.7 - tabulate (0.8.7) 492.1 39.5 - PrettyTable (0.7.2) 945.5 76.0 - texttable (1.6.2) 1239.5 99.6 - =========================== ========== =========== - - -Version history ---------------- - -The full version history can be found at the [changelog](https://github.com/astanin/python-tabulate/blob/master/CHANGELOG). - -How to contribute ------------------ - -Contributions should include tests and an explanation for the changes -they propose. Documentation (examples, docstrings, README.md) should be -updated accordingly. - -This project uses [nose](https://nose.readthedocs.org/) testing -framework and [tox](https://tox.readthedocs.io/) to automate testing in -different environments. Add tests to one of the files in the `test/` -folder. - -To run tests on all supported Python versions, make sure all Python -interpreters, `nose` and `tox` are installed, then run `tox` in the root -of the project source tree. - -On Linux `tox` expects to find executables like `python2.6`, -`python2.7`, `python3.4` etc. On Windows it looks for -`C:\Python26\python.exe`, `C:\Python27\python.exe` and -`C:\Python34\python.exe` respectively. - -To test only some Python environements, use `-e` option. For example, to -test only against Python 2.7 and Python 3.6, run: - - tox -e py27,py36 - -in the root of the project source tree. - -To enable NumPy and Pandas tests, run: - - tox -e py27-extra,py36-extra - -(this may take a long time the first time, because NumPy and Pandas will -have to be installed in the new virtual environments) - -See `tox.ini` file to learn how to use `nosetests` directly to test -individual Python versions. - -Contributors ------------- - -Sergey Astanin, Pau Tallada Crespí, Erwin Marsi, Mik Kocikowski, Bill -Ryder, Zach Dwiel, Frederik Rietdijk, Philipp Bogensberger, Greg -(anonymous), Stefan Tatschner, Emiel van Miltenburg, Brandon Bennett, -Amjith Ramanujam, Jan Schulz, Simon Percivall, Javier Santacruz -López-Cepero, Sam Denton, Alexey Ziyangirov, acaird, Cesar Sanchez, -naught101, John Vandenberg, Zack Dever, Christian Clauss, Benjamin -Maier, Andy MacKinlay, Thomas Roten, Jue Wang, Joe King, Samuel Phan, -Nick Satterly, Daniel Robbins, Dmitry B, Lars Butler, Andreas Maier, -Dick Marinus, Sébastien Celles, Yago González, Andrew Gaul, Wim Glenn, -Jean Michel Rouly, Tim Gates, John Vandenberg, Sorin Sbarnea, -Wes Turner, Andrew Tija, Marco Gorelli, Sean McGinnis, danja100. diff --git a/jans-cli-tui/cli/pylib/tabulate/__init__.py b/jans-cli-tui/cli/pylib/tabulate/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/jans-cli-tui/cli/pylib/tabulate/tabulate.py b/jans-cli-tui/cli/pylib/tabulate/tabulate.py deleted file mode 100644 index 2bbb913c62d..00000000000 --- a/jans-cli-tui/cli/pylib/tabulate/tabulate.py +++ /dev/null @@ -1,1792 +0,0 @@ -# -*- coding: utf-8 -*- - -"""Pretty-print tabular data.""" - -from __future__ import print_function -from __future__ import unicode_literals -from collections import namedtuple -from platform import python_version_tuple -import re -import math -import sys - - -if sys.version_info.major >= 3 and sys.version_info.minor >= 3: - from collections.abc import Iterable -else: - from collections import Iterable - - -if python_version_tuple()[0] < "3": - from itertools import izip_longest - from functools import partial - - _none_type = type(None) - _bool_type = bool - _int_type = int - _long_type = long # noqa - _float_type = float - _text_type = unicode # noqa - _binary_type = str - - def _is_file(f): - return hasattr(f, "read") - - -else: - from itertools import zip_longest as izip_longest - from functools import reduce, partial - - _none_type = type(None) - _bool_type = bool - _int_type = int - _long_type = int - _float_type = float - _text_type = str - _binary_type = bytes - basestring = str - - import io - - def _is_file(f): - return isinstance(f, io.IOBase) - - -try: - import wcwidth # optional wide-character (CJK) support -except ImportError: - wcwidth = None - -try: - from html import escape as htmlescape -except ImportError: - from cgi import escape as htmlescape - - -__all__ = ["tabulate", "tabulate_formats", "simple_separated_format"] -__version__ = "0.8.8" - - -# minimum extra space in headers -MIN_PADDING = 2 - -# Whether or not to preserve leading/trailing whitespace in data. -PRESERVE_WHITESPACE = False - -_DEFAULT_FLOATFMT = "g" -_DEFAULT_MISSINGVAL = "" - - -# if True, enable wide-character (CJK) support -WIDE_CHARS_MODE = wcwidth is not None - - -Line = namedtuple("Line", ["begin", "hline", "sep", "end"]) - - -DataRow = namedtuple("DataRow", ["begin", "sep", "end"]) - - -# A table structure is suppposed to be: -# -# --- lineabove --------- -# headerrow -# --- linebelowheader --- -# datarow -# --- linebewteenrows --- -# ... (more datarows) ... -# --- linebewteenrows --- -# last datarow -# --- linebelow --------- -# -# TableFormat's line* elements can be -# -# - either None, if the element is not used, -# - or a Line tuple, -# - or a function: [col_widths], [col_alignments] -> string. -# -# TableFormat's *row elements can be -# -# - either None, if the element is not used, -# - or a DataRow tuple, -# - or a function: [cell_values], [col_widths], [col_alignments] -> string. -# -# padding (an integer) is the amount of white space around data values. -# -# with_header_hide: -# -# - either None, to display all table elements unconditionally, -# - or a list of elements not to be displayed if the table has column headers. -# -TableFormat = namedtuple( - "TableFormat", - [ - "lineabove", - "linebelowheader", - "linebetweenrows", - "linebelow", - "headerrow", - "datarow", - "padding", - "with_header_hide", - ], -) - - -def _pipe_segment_with_colons(align, colwidth): - """Return a segment of a horizontal line with optional colons which - indicate column's alignment (as in `pipe` output format).""" - w = colwidth - if align in ["right", "decimal"]: - return ("-" * (w - 1)) + ":" - elif align == "center": - return ":" + ("-" * (w - 2)) + ":" - elif align == "left": - return ":" + ("-" * (w - 1)) - else: - return "-" * w - - -def _pipe_line_with_colons(colwidths, colaligns): - """Return a horizontal line with optional colons to indicate column's - alignment (as in `pipe` output format).""" - if not colaligns: # e.g. printing an empty data frame (github issue #15) - colaligns = [""] * len(colwidths) - segments = [_pipe_segment_with_colons(a, w) for a, w in zip(colaligns, colwidths)] - return "|" + "|".join(segments) + "|" - - -def _mediawiki_row_with_attrs(separator, cell_values, colwidths, colaligns): - alignment = { - "left": "", - "right": 'align="right"| ', - "center": 'align="center"| ', - "decimal": 'align="right"| ', - } - # hard-coded padding _around_ align attribute and value together - # rather than padding parameter which affects only the value - values_with_attrs = [ - " " + alignment.get(a, "") + c + " " for c, a in zip(cell_values, colaligns) - ] - colsep = separator * 2 - return (separator + colsep.join(values_with_attrs)).rstrip() - - -def _textile_row_with_attrs(cell_values, colwidths, colaligns): - cell_values[0] += " " - alignment = {"left": "<.", "right": ">.", "center": "=.", "decimal": ">."} - values = (alignment.get(a, "") + v for a, v in zip(colaligns, cell_values)) - return "|" + "|".join(values) + "|" - - -def _html_begin_table_without_header(colwidths_ignore, colaligns_ignore): - # this table header will be suppressed if there is a header row - return "\n" - - -def _html_row_with_attrs(celltag, unsafe, cell_values, colwidths, colaligns): - alignment = { - "left": "", - "right": ' style="text-align: right;"', - "center": ' style="text-align: center;"', - "decimal": ' style="text-align: right;"', - } - if unsafe: - values_with_attrs = [ - "<{0}{1}>{2}".format(celltag, alignment.get(a, ""), c) - for c, a in zip(cell_values, colaligns) - ] - else: - values_with_attrs = [ - "<{0}{1}>{2}".format(celltag, alignment.get(a, ""), htmlescape(c)) - for c, a in zip(cell_values, colaligns) - ] - rowhtml = "{}".format("".join(values_with_attrs).rstrip()) - if celltag == "th": # it's a header row, create a new table header - rowhtml = "
\n\n{}\n\n".format(rowhtml) - return rowhtml - - -def _moin_row_with_attrs(celltag, cell_values, colwidths, colaligns, header=""): - alignment = { - "left": "", - "right": '', - "center": '', - "decimal": '', - } - values_with_attrs = [ - "{0}{1} {2} ".format(celltag, alignment.get(a, ""), header + c + header) - for c, a in zip(cell_values, colaligns) - ] - return "".join(values_with_attrs) + "||" - - -def _latex_line_begin_tabular(colwidths, colaligns, booktabs=False): - alignment = {"left": "l", "right": "r", "center": "c", "decimal": "r"} - tabular_columns_fmt = "".join([alignment.get(a, "l") for a in colaligns]) - return "\n".join( - [ - "\\begin{tabular}{" + tabular_columns_fmt + "}", - "\\toprule" if booktabs else "\\hline", - ] - ) - - -LATEX_ESCAPE_RULES = { - r"&": r"\&", - r"%": r"\%", - r"$": r"\$", - r"#": r"\#", - r"_": r"\_", - r"^": r"\^{}", - r"{": r"\{", - r"}": r"\}", - r"~": r"\textasciitilde{}", - "\\": r"\textbackslash{}", - r"<": r"\ensuremath{<}", - r">": r"\ensuremath{>}", -} - - -def _latex_row(cell_values, colwidths, colaligns, escrules=LATEX_ESCAPE_RULES): - def escape_char(c): - return escrules.get(c, c) - - escaped_values = ["".join(map(escape_char, cell)) for cell in cell_values] - rowfmt = DataRow("", "&", "\\\\") - return _build_simple_row(escaped_values, rowfmt) - - -def _rst_escape_first_column(rows, headers): - def escape_empty(val): - if isinstance(val, (_text_type, _binary_type)) and not val.strip(): - return ".." - else: - return val - - new_headers = list(headers) - new_rows = [] - if headers: - new_headers[0] = escape_empty(headers[0]) - for row in rows: - new_row = list(row) - if new_row: - new_row[0] = escape_empty(row[0]) - new_rows.append(new_row) - return new_rows, new_headers - - -_table_formats = { - "simple": TableFormat( - lineabove=Line("", "-", " ", ""), - linebelowheader=Line("", "-", " ", ""), - linebetweenrows=None, - linebelow=Line("", "-", " ", ""), - headerrow=DataRow("", " ", ""), - datarow=DataRow("", " ", ""), - padding=0, - with_header_hide=["lineabove", "linebelow"], - ), - "plain": TableFormat( - lineabove=None, - linebelowheader=None, - linebetweenrows=None, - linebelow=None, - headerrow=DataRow("", " ", ""), - datarow=DataRow("", " ", ""), - padding=0, - with_header_hide=None, - ), - "grid": TableFormat( - lineabove=Line("+", "-", "+", "+"), - linebelowheader=Line("+", "=", "+", "+"), - linebetweenrows=Line("+", "-", "+", "+"), - linebelow=Line("+", "-", "+", "+"), - headerrow=DataRow("|", "|", "|"), - datarow=DataRow("|", "|", "|"), - padding=1, - with_header_hide=None, - ), - "fancy_grid": TableFormat( - lineabove=Line("╒", "═", "╤", "╕"), - linebelowheader=Line("╞", "═", "╪", "╡"), - linebetweenrows=Line("├", "─", "┼", "┤"), - linebelow=Line("╘", "═", "╧", "╛"), - headerrow=DataRow("│", "│", "│"), - datarow=DataRow("│", "│", "│"), - padding=1, - with_header_hide=None, - ), - "github": TableFormat( - lineabove=Line("|", "-", "|", "|"), - linebelowheader=Line("|", "-", "|", "|"), - linebetweenrows=None, - linebelow=None, - headerrow=DataRow("|", "|", "|"), - datarow=DataRow("|", "|", "|"), - padding=1, - with_header_hide=["lineabove"], - ), - "pipe": TableFormat( - lineabove=_pipe_line_with_colons, - linebelowheader=_pipe_line_with_colons, - linebetweenrows=None, - linebelow=None, - headerrow=DataRow("|", "|", "|"), - datarow=DataRow("|", "|", "|"), - padding=1, - with_header_hide=["lineabove"], - ), - "orgtbl": TableFormat( - lineabove=None, - linebelowheader=Line("|", "-", "+", "|"), - linebetweenrows=None, - linebelow=None, - headerrow=DataRow("|", "|", "|"), - datarow=DataRow("|", "|", "|"), - padding=1, - with_header_hide=None, - ), - "jira": TableFormat( - lineabove=None, - linebelowheader=None, - linebetweenrows=None, - linebelow=None, - headerrow=DataRow("||", "||", "||"), - datarow=DataRow("|", "|", "|"), - padding=1, - with_header_hide=None, - ), - "presto": TableFormat( - lineabove=None, - linebelowheader=Line("", "-", "+", ""), - linebetweenrows=None, - linebelow=None, - headerrow=DataRow("", "|", ""), - datarow=DataRow("", "|", ""), - padding=1, - with_header_hide=None, - ), - "pretty": TableFormat( - lineabove=Line("+", "-", "+", "+"), - linebelowheader=Line("+", "-", "+", "+"), - linebetweenrows=None, - linebelow=Line("+", "-", "+", "+"), - headerrow=DataRow("|", "|", "|"), - datarow=DataRow("|", "|", "|"), - padding=1, - with_header_hide=None, - ), - "psql": TableFormat( - lineabove=Line("+", "-", "+", "+"), - linebelowheader=Line("|", "-", "+", "|"), - linebetweenrows=None, - linebelow=Line("+", "-", "+", "+"), - headerrow=DataRow("|", "|", "|"), - datarow=DataRow("|", "|", "|"), - padding=1, - with_header_hide=None, - ), - "rst": TableFormat( - lineabove=Line("", "=", " ", ""), - linebelowheader=Line("", "=", " ", ""), - linebetweenrows=None, - linebelow=Line("", "=", " ", ""), - headerrow=DataRow("", " ", ""), - datarow=DataRow("", " ", ""), - padding=0, - with_header_hide=None, - ), - "mediawiki": TableFormat( - lineabove=Line( - '{| class="wikitable" style="text-align: left;"', - "", - "", - "\n|+ \n|-", - ), - linebelowheader=Line("|-", "", "", ""), - linebetweenrows=Line("|-", "", "", ""), - linebelow=Line("|}", "", "", ""), - headerrow=partial(_mediawiki_row_with_attrs, "!"), - datarow=partial(_mediawiki_row_with_attrs, "|"), - padding=0, - with_header_hide=None, - ), - "moinmoin": TableFormat( - lineabove=None, - linebelowheader=None, - linebetweenrows=None, - linebelow=None, - headerrow=partial(_moin_row_with_attrs, "||", header="'''"), - datarow=partial(_moin_row_with_attrs, "||"), - padding=1, - with_header_hide=None, - ), - "youtrack": TableFormat( - lineabove=None, - linebelowheader=None, - linebetweenrows=None, - linebelow=None, - headerrow=DataRow("|| ", " || ", " || "), - datarow=DataRow("| ", " | ", " |"), - padding=1, - with_header_hide=None, - ), - "html": TableFormat( - lineabove=_html_begin_table_without_header, - linebelowheader="", - linebetweenrows=None, - linebelow=Line("\n
", "", "", ""), - headerrow=partial(_html_row_with_attrs, "th", False), - datarow=partial(_html_row_with_attrs, "td", False), - padding=0, - with_header_hide=["lineabove"], - ), - "unsafehtml": TableFormat( - lineabove=_html_begin_table_without_header, - linebelowheader="", - linebetweenrows=None, - linebelow=Line("\n", "", "", ""), - headerrow=partial(_html_row_with_attrs, "th", True), - datarow=partial(_html_row_with_attrs, "td", True), - padding=0, - with_header_hide=["lineabove"], - ), - "latex": TableFormat( - lineabove=_latex_line_begin_tabular, - linebelowheader=Line("\\hline", "", "", ""), - linebetweenrows=None, - linebelow=Line("\\hline\n\\end{tabular}", "", "", ""), - headerrow=_latex_row, - datarow=_latex_row, - padding=1, - with_header_hide=None, - ), - "latex_raw": TableFormat( - lineabove=_latex_line_begin_tabular, - linebelowheader=Line("\\hline", "", "", ""), - linebetweenrows=None, - linebelow=Line("\\hline\n\\end{tabular}", "", "", ""), - headerrow=partial(_latex_row, escrules={}), - datarow=partial(_latex_row, escrules={}), - padding=1, - with_header_hide=None, - ), - "latex_booktabs": TableFormat( - lineabove=partial(_latex_line_begin_tabular, booktabs=True), - linebelowheader=Line("\\midrule", "", "", ""), - linebetweenrows=None, - linebelow=Line("\\bottomrule\n\\end{tabular}", "", "", ""), - headerrow=_latex_row, - datarow=_latex_row, - padding=1, - with_header_hide=None, - ), - "tsv": TableFormat( - lineabove=None, - linebelowheader=None, - linebetweenrows=None, - linebelow=None, - headerrow=DataRow("", "\t", ""), - datarow=DataRow("", "\t", ""), - padding=0, - with_header_hide=None, - ), - "textile": TableFormat( - lineabove=None, - linebelowheader=None, - linebetweenrows=None, - linebelow=None, - headerrow=DataRow("|_. ", "|_.", "|"), - datarow=_textile_row_with_attrs, - padding=1, - with_header_hide=None, - ), -} - - -tabulate_formats = list(sorted(_table_formats.keys())) - -# The table formats for which multiline cells will be folded into subsequent -# table rows. The key is the original format specified at the API. The value is -# the format that will be used to represent the original format. -multiline_formats = { - "plain": "plain", - "simple": "simple", - "grid": "grid", - "fancy_grid": "fancy_grid", - "pipe": "pipe", - "orgtbl": "orgtbl", - "jira": "jira", - "presto": "presto", - "pretty": "pretty", - "psql": "psql", - "rst": "rst", -} - -# TODO: Add multiline support for the remaining table formats: -# - mediawiki: Replace \n with
-# - moinmoin: TBD -# - youtrack: TBD -# - html: Replace \n with
-# - latex*: Use "makecell" package: In header, replace X\nY with -# \thead{X\\Y} and in data row, replace X\nY with \makecell{X\\Y} -# - tsv: TBD -# - textile: Replace \n with
(must be well-formed XML) - -_multiline_codes = re.compile(r"\r|\n|\r\n") -_multiline_codes_bytes = re.compile(b"\r|\n|\r\n") -_invisible_codes = re.compile( - r"\x1b\[\d+[;\d]*m|\x1b\[\d*\;\d*\;\d*m" -) # ANSI color codes -_invisible_codes_bytes = re.compile( - b"\x1b\\[\\d+\\[;\\d]*m|\x1b\\[\\d*;\\d*;\\d*m" -) # ANSI color codes - - -def simple_separated_format(separator): - """Construct a simple TableFormat with columns separated by a separator. - - >>> tsv = simple_separated_format("\\t") ; \ - tabulate([["foo", 1], ["spam", 23]], tablefmt=tsv) == 'foo \\t 1\\nspam\\t23' - True - - """ - return TableFormat( - None, - None, - None, - None, - headerrow=DataRow("", separator, ""), - datarow=DataRow("", separator, ""), - padding=0, - with_header_hide=None, - ) - - -def _isconvertible(conv, string): - try: - conv(string) - return True - except (ValueError, TypeError): - return False - - -def _isnumber(string): - """ - >>> _isnumber("123.45") - True - >>> _isnumber("123") - True - >>> _isnumber("spam") - False - >>> _isnumber("123e45678") - False - >>> _isnumber("inf") - True - """ - if not _isconvertible(float, string): - return False - elif isinstance(string, (_text_type, _binary_type)) and ( - math.isinf(float(string)) or math.isnan(float(string)) - ): - return string.lower() in ["inf", "-inf", "nan"] - return True - - -def _isint(string, inttype=int): - """ - >>> _isint("123") - True - >>> _isint("123.45") - False - """ - return ( - type(string) is inttype - or (isinstance(string, _binary_type) or isinstance(string, _text_type)) - and _isconvertible(inttype, string) - ) - - -def _isbool(string): - """ - >>> _isbool(True) - True - >>> _isbool("False") - True - >>> _isbool(1) - False - """ - return type(string) is _bool_type or ( - isinstance(string, (_binary_type, _text_type)) and string in ("True", "False") - ) - - -def _type(string, has_invisible=True, numparse=True): - """The least generic type (type(None), int, float, str, unicode). - - >>> _type(None) is type(None) - True - >>> _type("foo") is type("") - True - >>> _type("1") is type(1) - True - >>> _type('\x1b[31m42\x1b[0m') is type(42) - True - >>> _type('\x1b[31m42\x1b[0m') is type(42) - True - - """ - - if has_invisible and ( - isinstance(string, _text_type) or isinstance(string, _binary_type) - ): - string = _strip_invisible(string) - - if string is None: - return _none_type - elif hasattr(string, "isoformat"): # datetime.datetime, date, and time - return _text_type - elif _isbool(string): - return _bool_type - elif _isint(string) and numparse: - return int - elif _isint(string, _long_type) and numparse: - return int - elif _isnumber(string) and numparse: - return float - elif isinstance(string, _binary_type): - return _binary_type - else: - return _text_type - - -def _afterpoint(string): - """Symbols after a decimal point, -1 if the string lacks the decimal point. - - >>> _afterpoint("123.45") - 2 - >>> _afterpoint("1001") - -1 - >>> _afterpoint("eggs") - -1 - >>> _afterpoint("123e45") - 2 - - """ - if _isnumber(string): - if _isint(string): - return -1 - else: - pos = string.rfind(".") - pos = string.lower().rfind("e") if pos < 0 else pos - if pos >= 0: - return len(string) - pos - 1 - else: - return -1 # no point - else: - return -1 # not a number - - -def _padleft(width, s): - """Flush right. - - >>> _padleft(6, '\u044f\u0439\u0446\u0430') == ' \u044f\u0439\u0446\u0430' - True - - """ - fmt = "{0:>%ds}" % width - return fmt.format(s) - - -def _padright(width, s): - """Flush left. - - >>> _padright(6, '\u044f\u0439\u0446\u0430') == '\u044f\u0439\u0446\u0430 ' - True - - """ - fmt = "{0:<%ds}" % width - return fmt.format(s) - - -def _padboth(width, s): - """Center string. - - >>> _padboth(6, '\u044f\u0439\u0446\u0430') == ' \u044f\u0439\u0446\u0430 ' - True - - """ - fmt = "{0:^%ds}" % width - return fmt.format(s) - - -def _padnone(ignore_width, s): - return s - - -def _strip_invisible(s): - "Remove invisible ANSI color codes." - if isinstance(s, _text_type): - return re.sub(_invisible_codes, "", s) - else: # a bytestring - return re.sub(_invisible_codes_bytes, "", s) - - -def _visible_width(s): - """Visible width of a printed string. ANSI color codes are removed. - - >>> _visible_width('\x1b[31mhello\x1b[0m'), _visible_width("world") - (5, 5) - - """ - # optional wide-character support - if wcwidth is not None and WIDE_CHARS_MODE: - len_fn = wcwidth.wcswidth - else: - len_fn = len - if isinstance(s, _text_type) or isinstance(s, _binary_type): - return len_fn(_strip_invisible(s)) - else: - return len_fn(_text_type(s)) - - -def _is_multiline(s): - if isinstance(s, _text_type): - return bool(re.search(_multiline_codes, s)) - else: # a bytestring - return bool(re.search(_multiline_codes_bytes, s)) - - -def _multiline_width(multiline_s, line_width_fn=len): - """Visible width of a potentially multiline content.""" - return max(map(line_width_fn, re.split("[\r\n]", multiline_s))) - - -def _choose_width_fn(has_invisible, enable_widechars, is_multiline): - """Return a function to calculate visible cell width.""" - if has_invisible: - line_width_fn = _visible_width - elif enable_widechars: # optional wide-character support if available - line_width_fn = wcwidth.wcswidth - else: - line_width_fn = len - if is_multiline: - width_fn = lambda s: _multiline_width(s, line_width_fn) # noqa - else: - width_fn = line_width_fn - return width_fn - - -def _align_column_choose_padfn(strings, alignment, has_invisible): - if alignment == "right": - if not PRESERVE_WHITESPACE: - strings = [s.strip() for s in strings] - padfn = _padleft - elif alignment == "center": - if not PRESERVE_WHITESPACE: - strings = [s.strip() for s in strings] - padfn = _padboth - elif alignment == "decimal": - if has_invisible: - decimals = [_afterpoint(_strip_invisible(s)) for s in strings] - else: - decimals = [_afterpoint(s) for s in strings] - maxdecimals = max(decimals) - strings = [s + (maxdecimals - decs) * " " for s, decs in zip(strings, decimals)] - padfn = _padleft - elif not alignment: - padfn = _padnone - else: - if not PRESERVE_WHITESPACE: - strings = [s.strip() for s in strings] - padfn = _padright - return strings, padfn - - -def _align_column( - strings, - alignment, - minwidth=0, - has_invisible=True, - enable_widechars=False, - is_multiline=False, -): - """[string] -> [padded_string]""" - strings, padfn = _align_column_choose_padfn(strings, alignment, has_invisible) - width_fn = _choose_width_fn(has_invisible, enable_widechars, is_multiline) - - s_widths = list(map(width_fn, strings)) - maxwidth = max(max(s_widths), minwidth) - # TODO: refactor column alignment in single-line and multiline modes - if is_multiline: - if not enable_widechars and not has_invisible: - padded_strings = [ - "\n".join([padfn(maxwidth, s) for s in ms.splitlines()]) - for ms in strings - ] - else: - # enable wide-character width corrections - s_lens = [max((len(s) for s in re.split("[\r\n]", ms))) for ms in strings] - visible_widths = [maxwidth - (w - l) for w, l in zip(s_widths, s_lens)] - # wcswidth and _visible_width don't count invisible characters; - # padfn doesn't need to apply another correction - padded_strings = [ - "\n".join([padfn(w, s) for s in (ms.splitlines() or ms)]) - for ms, w in zip(strings, visible_widths) - ] - else: # single-line cell values - if not enable_widechars and not has_invisible: - padded_strings = [padfn(maxwidth, s) for s in strings] - else: - # enable wide-character width corrections - s_lens = list(map(len, strings)) - visible_widths = [maxwidth - (w - l) for w, l in zip(s_widths, s_lens)] - # wcswidth and _visible_width don't count invisible characters; - # padfn doesn't need to apply another correction - padded_strings = [padfn(w, s) for s, w in zip(strings, visible_widths)] - return padded_strings - - -def _more_generic(type1, type2): - types = { - _none_type: 0, - _bool_type: 1, - int: 2, - float: 3, - _binary_type: 4, - _text_type: 5, - } - invtypes = { - 5: _text_type, - 4: _binary_type, - 3: float, - 2: int, - 1: _bool_type, - 0: _none_type, - } - moregeneric = max(types.get(type1, 5), types.get(type2, 5)) - return invtypes[moregeneric] - - -def _column_type(strings, has_invisible=True, numparse=True): - """The least generic type all column values are convertible to. - - >>> _column_type([True, False]) is _bool_type - True - >>> _column_type(["1", "2"]) is _int_type - True - >>> _column_type(["1", "2.3"]) is _float_type - True - >>> _column_type(["1", "2.3", "four"]) is _text_type - True - >>> _column_type(["four", '\u043f\u044f\u0442\u044c']) is _text_type - True - >>> _column_type([None, "brux"]) is _text_type - True - >>> _column_type([1, 2, None]) is _int_type - True - >>> import datetime as dt - >>> _column_type([dt.datetime(1991,2,19), dt.time(17,35)]) is _text_type - True - - """ - types = [_type(s, has_invisible, numparse) for s in strings] - return reduce(_more_generic, types, _bool_type) - - -def _format(val, valtype, floatfmt, missingval="", has_invisible=True): - """Format a value accoding to its type. - - Unicode is supported: - - >>> hrow = ['\u0431\u0443\u043a\u0432\u0430', '\u0446\u0438\u0444\u0440\u0430'] ; \ - tbl = [['\u0430\u0437', 2], ['\u0431\u0443\u043a\u0438', 4]] ; \ - good_result = '\\u0431\\u0443\\u043a\\u0432\\u0430 \\u0446\\u0438\\u0444\\u0440\\u0430\\n------- -------\\n\\u0430\\u0437 2\\n\\u0431\\u0443\\u043a\\u0438 4' ; \ - tabulate(tbl, headers=hrow) == good_result - True - - """ # noqa - if val is None: - return missingval - - if valtype in [int, _text_type]: - return "{0}".format(val) - elif valtype is _binary_type: - try: - return _text_type(val, "ascii") - except TypeError: - return _text_type(val) - elif valtype is float: - is_a_colored_number = has_invisible and isinstance( - val, (_text_type, _binary_type) - ) - if is_a_colored_number: - raw_val = _strip_invisible(val) - formatted_val = format(float(raw_val), floatfmt) - return val.replace(raw_val, formatted_val) - else: - return format(float(val), floatfmt) - else: - return "{0}".format(val) - - -def _align_header( - header, alignment, width, visible_width, is_multiline=False, width_fn=None -): - "Pad string header to width chars given known visible_width of the header." - if is_multiline: - header_lines = re.split(_multiline_codes, header) - padded_lines = [ - _align_header(h, alignment, width, width_fn(h)) for h in header_lines - ] - return "\n".join(padded_lines) - # else: not multiline - ninvisible = len(header) - visible_width - width += ninvisible - if alignment == "left": - return _padright(width, header) - elif alignment == "center": - return _padboth(width, header) - elif not alignment: - return "{0}".format(header) - else: - return _padleft(width, header) - - -def _prepend_row_index(rows, index): - """Add a left-most index column.""" - if index is None or index is False: - return rows - if len(index) != len(rows): - print("index=", index) - print("rows=", rows) - raise ValueError("index must be as long as the number of data rows") - rows = [[v] + list(row) for v, row in zip(index, rows)] - return rows - - -def _bool(val): - "A wrapper around standard bool() which doesn't throw on NumPy arrays" - try: - return bool(val) - except ValueError: # val is likely to be a numpy array with many elements - return False - - -def _normalize_tabular_data(tabular_data, headers, showindex="default"): - """Transform a supported data type to a list of lists, and a list of headers. - - Supported tabular data types: - - * list-of-lists or another iterable of iterables - - * list of named tuples (usually used with headers="keys") - - * list of dicts (usually used with headers="keys") - - * list of OrderedDicts (usually used with headers="keys") - - * 2D NumPy arrays - - * NumPy record arrays (usually used with headers="keys") - - * dict of iterables (usually used with headers="keys") - - * pandas.DataFrame (usually used with headers="keys") - - The first row can be used as headers if headers="firstrow", - column indices can be used as headers if headers="keys". - - If showindex="default", show row indices of the pandas.DataFrame. - If showindex="always", show row indices for all types of data. - If showindex="never", don't show row indices for all types of data. - If showindex is an iterable, show its values as row indices. - - """ - - try: - bool(headers) - is_headers2bool_broken = False # noqa - except ValueError: # numpy.ndarray, pandas.core.index.Index, ... - is_headers2bool_broken = True # noqa - headers = list(headers) - - index = None - if hasattr(tabular_data, "keys") and hasattr(tabular_data, "values"): - # dict-like and pandas.DataFrame? - if hasattr(tabular_data.values, "__call__"): - # likely a conventional dict - keys = tabular_data.keys() - rows = list( - izip_longest(*tabular_data.values()) - ) # columns have to be transposed - elif hasattr(tabular_data, "index"): - # values is a property, has .index => it's likely a pandas.DataFrame (pandas 0.11.0) - keys = list(tabular_data) - if tabular_data.index.name is not None: - if isinstance(tabular_data.index.name, list): - keys[:0] = tabular_data.index.name - else: - keys[:0] = [tabular_data.index.name] - vals = tabular_data.values # values matrix doesn't need to be transposed - # for DataFrames add an index per default - index = list(tabular_data.index) - rows = [list(row) for row in vals] - else: - raise ValueError("tabular data doesn't appear to be a dict or a DataFrame") - - if headers == "keys": - headers = list(map(_text_type, keys)) # headers should be strings - - else: # it's a usual an iterable of iterables, or a NumPy array - rows = list(tabular_data) - - if headers == "keys" and not rows: - # an empty table (issue #81) - headers = [] - elif ( - headers == "keys" - and hasattr(tabular_data, "dtype") - and getattr(tabular_data.dtype, "names") - ): - # numpy record array - headers = tabular_data.dtype.names - elif ( - headers == "keys" - and len(rows) > 0 - and isinstance(rows[0], tuple) - and hasattr(rows[0], "_fields") - ): - # namedtuple - headers = list(map(_text_type, rows[0]._fields)) - elif len(rows) > 0 and isinstance(rows[0], dict): - # dict or OrderedDict - uniq_keys = set() # implements hashed lookup - keys = [] # storage for set - if headers == "firstrow": - firstdict = rows[0] if len(rows) > 0 else {} - keys.extend(firstdict.keys()) - uniq_keys.update(keys) - rows = rows[1:] - for row in rows: - for k in row.keys(): - # Save unique items in input order - if k not in uniq_keys: - keys.append(k) - uniq_keys.add(k) - if headers == "keys": - headers = keys - elif isinstance(headers, dict): - # a dict of headers for a list of dicts - headers = [headers.get(k, k) for k in keys] - headers = list(map(_text_type, headers)) - elif headers == "firstrow": - if len(rows) > 0: - headers = [firstdict.get(k, k) for k in keys] - headers = list(map(_text_type, headers)) - else: - headers = [] - elif headers: - raise ValueError( - "headers for a list of dicts is not a dict or a keyword" - ) - rows = [[row.get(k) for k in keys] for row in rows] - - elif ( - headers == "keys" - and hasattr(tabular_data, "description") - and hasattr(tabular_data, "fetchone") - and hasattr(tabular_data, "rowcount") - ): - # Python Database API cursor object (PEP 0249) - # print tabulate(cursor, headers='keys') - headers = [column[0] for column in tabular_data.description] - - elif headers == "keys" and len(rows) > 0: - # keys are column indices - headers = list(map(_text_type, range(len(rows[0])))) - - # take headers from the first row if necessary - if headers == "firstrow" and len(rows) > 0: - if index is not None: - headers = [index[0]] + list(rows[0]) - index = index[1:] - else: - headers = rows[0] - headers = list(map(_text_type, headers)) # headers should be strings - rows = rows[1:] - - headers = list(map(_text_type, headers)) - rows = list(map(list, rows)) - - # add or remove an index column - showindex_is_a_str = type(showindex) in [_text_type, _binary_type] - if showindex == "default" and index is not None: - rows = _prepend_row_index(rows, index) - elif isinstance(showindex, Iterable) and not showindex_is_a_str: - rows = _prepend_row_index(rows, list(showindex)) - elif showindex == "always" or (_bool(showindex) and not showindex_is_a_str): - if index is None: - index = list(range(len(rows))) - rows = _prepend_row_index(rows, index) - elif showindex == "never" or (not _bool(showindex) and not showindex_is_a_str): - pass - - # pad with empty headers for initial columns if necessary - if headers and len(rows) > 0: - nhs = len(headers) - ncols = len(rows[0]) - if nhs < ncols: - headers = [""] * (ncols - nhs) + headers - - return rows, headers - - -def tabulate( - tabular_data, - headers=(), - tablefmt="simple", - floatfmt=_DEFAULT_FLOATFMT, - numalign="decimal", - stralign="left", - missingval=_DEFAULT_MISSINGVAL, - showindex="default", - disable_numparse=False, - colalign=None, -): - """Format a fixed width table for pretty printing. - - >>> print(tabulate([[1, 2.34], [-56, "8.999"], ["2", "10001"]])) - --- --------- - 1 2.34 - -56 8.999 - 2 10001 - --- --------- - - The first required argument (`tabular_data`) can be a - list-of-lists (or another iterable of iterables), a list of named - tuples, a dictionary of iterables, an iterable of dictionaries, - a two-dimensional NumPy array, NumPy record array, or a Pandas' - dataframe. - - - Table headers - ------------- - - To print nice column headers, supply the second argument (`headers`): - - - `headers` can be an explicit list of column headers - - if `headers="firstrow"`, then the first row of data is used - - if `headers="keys"`, then dictionary keys or column indices are used - - Otherwise a headerless table is produced. - - If the number of headers is less than the number of columns, they - are supposed to be names of the last columns. This is consistent - with the plain-text format of R and Pandas' dataframes. - - >>> print(tabulate([["sex","age"],["Alice","F",24],["Bob","M",19]], - ... headers="firstrow")) - sex age - ----- ----- ----- - Alice F 24 - Bob M 19 - - By default, pandas.DataFrame data have an additional column called - row index. To add a similar column to all other types of data, - use `showindex="always"` or `showindex=True`. To suppress row indices - for all types of data, pass `showindex="never" or `showindex=False`. - To add a custom row index column, pass `showindex=some_iterable`. - - >>> print(tabulate([["F",24],["M",19]], showindex="always")) - - - -- - 0 F 24 - 1 M 19 - - - -- - - - Column alignment - ---------------- - - `tabulate` tries to detect column types automatically, and aligns - the values properly. By default it aligns decimal points of the - numbers (or flushes integer numbers to the right), and flushes - everything else to the left. Possible column alignments - (`numalign`, `stralign`) are: "right", "center", "left", "decimal" - (only for `numalign`), and None (to disable alignment). - - - Table formats - ------------- - - `floatfmt` is a format specification used for columns which - contain numeric data with a decimal point. This can also be - a list or tuple of format strings, one per column. - - `None` values are replaced with a `missingval` string (like - `floatfmt`, this can also be a list of values for different - columns): - - >>> print(tabulate([["spam", 1, None], - ... ["eggs", 42, 3.14], - ... ["other", None, 2.7]], missingval="?")) - ----- -- ---- - spam 1 ? - eggs 42 3.14 - other ? 2.7 - ----- -- ---- - - Various plain-text table formats (`tablefmt`) are supported: - 'plain', 'simple', 'grid', 'pipe', 'orgtbl', 'rst', 'mediawiki', - 'latex', 'latex_raw' and 'latex_booktabs'. Variable `tabulate_formats` - contains the list of currently supported formats. - - "plain" format doesn't use any pseudographics to draw tables, - it separates columns with a double space: - - >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], - ... ["strings", "numbers"], "plain")) - strings numbers - spam 41.9999 - eggs 451 - - >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="plain")) - spam 41.9999 - eggs 451 - - "simple" format is like Pandoc simple_tables: - - >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], - ... ["strings", "numbers"], "simple")) - strings numbers - --------- --------- - spam 41.9999 - eggs 451 - - >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="simple")) - ---- -------- - spam 41.9999 - eggs 451 - ---- -------- - - "grid" is similar to tables produced by Emacs table.el package or - Pandoc grid_tables: - - >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], - ... ["strings", "numbers"], "grid")) - +-----------+-----------+ - | strings | numbers | - +===========+===========+ - | spam | 41.9999 | - +-----------+-----------+ - | eggs | 451 | - +-----------+-----------+ - - >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="grid")) - +------+----------+ - | spam | 41.9999 | - +------+----------+ - | eggs | 451 | - +------+----------+ - - "fancy_grid" draws a grid using box-drawing characters: - - >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], - ... ["strings", "numbers"], "fancy_grid")) - ╒═══════════╤═══════════╕ - │ strings │ numbers │ - ╞═══════════╪═══════════╡ - │ spam │ 41.9999 │ - ├───────────┼───────────┤ - │ eggs │ 451 │ - ╘═══════════╧═══════════╛ - - "pipe" is like tables in PHP Markdown Extra extension or Pandoc - pipe_tables: - - >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], - ... ["strings", "numbers"], "pipe")) - | strings | numbers | - |:----------|----------:| - | spam | 41.9999 | - | eggs | 451 | - - "presto" is like tables produce by the Presto CLI: - - >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], - ... ["strings", "numbers"], "presto")) - strings | numbers - -----------+----------- - spam | 41.9999 - eggs | 451 - - >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="pipe")) - |:-----|---------:| - | spam | 41.9999 | - | eggs | 451 | - - "orgtbl" is like tables in Emacs org-mode and orgtbl-mode. They - are slightly different from "pipe" format by not using colons to - define column alignment, and using a "+" sign to indicate line - intersections: - - >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], - ... ["strings", "numbers"], "orgtbl")) - | strings | numbers | - |-----------+-----------| - | spam | 41.9999 | - | eggs | 451 | - - - >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="orgtbl")) - | spam | 41.9999 | - | eggs | 451 | - - "rst" is like a simple table format from reStructuredText; please - note that reStructuredText accepts also "grid" tables: - - >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], - ... ["strings", "numbers"], "rst")) - ========= ========= - strings numbers - ========= ========= - spam 41.9999 - eggs 451 - ========= ========= - - >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="rst")) - ==== ======== - spam 41.9999 - eggs 451 - ==== ======== - - "mediawiki" produces a table markup used in Wikipedia and on other - MediaWiki-based sites: - - >>> print(tabulate([["strings", "numbers"], ["spam", 41.9999], ["eggs", "451.0"]], - ... headers="firstrow", tablefmt="mediawiki")) - {| class="wikitable" style="text-align: left;" - |+ - |- - ! strings !! align="right"| numbers - |- - | spam || align="right"| 41.9999 - |- - | eggs || align="right"| 451 - |} - - "html" produces HTML markup as an html.escape'd str - with a ._repr_html_ method so that Jupyter Lab and Notebook display the HTML - and a .str property so that the raw HTML remains accessible - the unsafehtml table format can be used if an unescaped HTML format is required: - - >>> print(tabulate([["strings", "numbers"], ["spam", 41.9999], ["eggs", "451.0"]], - ... headers="firstrow", tablefmt="html")) - - - - - - - - -
strings numbers
spam 41.9999
eggs 451
- - "latex" produces a tabular environment of LaTeX document markup: - - >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="latex")) - \\begin{tabular}{lr} - \\hline - spam & 41.9999 \\\\ - eggs & 451 \\\\ - \\hline - \\end{tabular} - - "latex_raw" is similar to "latex", but doesn't escape special characters, - such as backslash and underscore, so LaTeX commands may embedded into - cells' values: - - >>> print(tabulate([["spam$_9$", 41.9999], ["\\\\emph{eggs}", "451.0"]], tablefmt="latex_raw")) - \\begin{tabular}{lr} - \\hline - spam$_9$ & 41.9999 \\\\ - \\emph{eggs} & 451 \\\\ - \\hline - \\end{tabular} - - "latex_booktabs" produces a tabular environment of LaTeX document markup - using the booktabs.sty package: - - >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="latex_booktabs")) - \\begin{tabular}{lr} - \\toprule - spam & 41.9999 \\\\ - eggs & 451 \\\\ - \\bottomrule - \\end{tabular} - - Number parsing - -------------- - By default, anything which can be parsed as a number is a number. - This ensures numbers represented as strings are aligned properly. - This can lead to weird results for particular strings such as - specific git SHAs e.g. "42992e1" will be parsed into the number - 429920 and aligned as such. - - To completely disable number parsing (and alignment), use - `disable_numparse=True`. For more fine grained control, a list column - indices is used to disable number parsing only on those columns - e.g. `disable_numparse=[0, 2]` would disable number parsing only on the - first and third columns. - """ - - if tabular_data is None: - tabular_data = [] - list_of_lists, headers = _normalize_tabular_data( - tabular_data, headers, showindex=showindex - ) - - # empty values in the first column of RST tables should be escaped (issue #82) - # "" should be escaped as "\\ " or ".." - if tablefmt == "rst": - list_of_lists, headers = _rst_escape_first_column(list_of_lists, headers) - - # PrettyTable formatting does not use any extra padding. - # Numbers are not parsed and are treated the same as strings for alignment. - # Check if pretty is the format being used and override the defaults so it - # does not impact other formats. - min_padding = MIN_PADDING - if tablefmt == "pretty": - min_padding = 0 - disable_numparse = True - numalign = "center" - stralign = "center" - - # optimization: look for ANSI control codes once, - # enable smart width functions only if a control code is found - plain_text = "\t".join( - ["\t".join(map(_text_type, headers))] - + ["\t".join(map(_text_type, row)) for row in list_of_lists] - ) - - has_invisible = re.search(_invisible_codes, plain_text) - enable_widechars = wcwidth is not None and WIDE_CHARS_MODE - if ( - not isinstance(tablefmt, TableFormat) - and tablefmt in multiline_formats - and _is_multiline(plain_text) - ): - tablefmt = multiline_formats.get(tablefmt, tablefmt) - is_multiline = True - else: - is_multiline = False - width_fn = _choose_width_fn(has_invisible, enable_widechars, is_multiline) - - # format rows and columns, convert numeric values to strings - cols = list(izip_longest(*list_of_lists)) - numparses = _expand_numparse(disable_numparse, len(cols)) - coltypes = [_column_type(col, numparse=np) for col, np in zip(cols, numparses)] - if isinstance(floatfmt, basestring): # old version - float_formats = len(cols) * [ - floatfmt - ] # just duplicate the string to use in each column - else: # if floatfmt is list, tuple etc we have one per column - float_formats = list(floatfmt) - if len(float_formats) < len(cols): - float_formats.extend((len(cols) - len(float_formats)) * [_DEFAULT_FLOATFMT]) - if isinstance(missingval, basestring): - missing_vals = len(cols) * [missingval] - else: - missing_vals = list(missingval) - if len(missing_vals) < len(cols): - missing_vals.extend((len(cols) - len(missing_vals)) * [_DEFAULT_MISSINGVAL]) - cols = [ - [_format(v, ct, fl_fmt, miss_v, has_invisible) for v in c] - for c, ct, fl_fmt, miss_v in zip(cols, coltypes, float_formats, missing_vals) - ] - - # align columns - aligns = [numalign if ct in [int, float] else stralign for ct in coltypes] - if colalign is not None: - assert isinstance(colalign, Iterable) - for idx, align in enumerate(colalign): - aligns[idx] = align - minwidths = ( - [width_fn(h) + min_padding for h in headers] if headers else [0] * len(cols) - ) - cols = [ - _align_column(c, a, minw, has_invisible, enable_widechars, is_multiline) - for c, a, minw in zip(cols, aligns, minwidths) - ] - - if headers: - # align headers and add headers - t_cols = cols or [[""]] * len(headers) - t_aligns = aligns or [stralign] * len(headers) - minwidths = [ - max(minw, max(width_fn(cl) for cl in c)) - for minw, c in zip(minwidths, t_cols) - ] - headers = [ - _align_header(h, a, minw, width_fn(h), is_multiline, width_fn) - for h, a, minw in zip(headers, t_aligns, minwidths) - ] - rows = list(zip(*cols)) - else: - minwidths = [max(width_fn(cl) for cl in c) for c in cols] - rows = list(zip(*cols)) - - if not isinstance(tablefmt, TableFormat): - tablefmt = _table_formats.get(tablefmt, _table_formats["simple"]) - - return _format_table(tablefmt, headers, rows, minwidths, aligns, is_multiline) - - -def _expand_numparse(disable_numparse, column_count): - """ - Return a list of bools of length `column_count` which indicates whether - number parsing should be used on each column. - If `disable_numparse` is a list of indices, each of those indices are False, - and everything else is True. - If `disable_numparse` is a bool, then the returned list is all the same. - """ - if isinstance(disable_numparse, Iterable): - numparses = [True] * column_count - for index in disable_numparse: - numparses[index] = False - return numparses - else: - return [not disable_numparse] * column_count - - -def _pad_row(cells, padding): - if cells: - pad = " " * padding - padded_cells = [pad + cell + pad for cell in cells] - return padded_cells - else: - return cells - - -def _build_simple_row(padded_cells, rowfmt): - "Format row according to DataRow format without padding." - begin, sep, end = rowfmt - return (begin + sep.join(padded_cells) + end).rstrip() - - -def _build_row(padded_cells, colwidths, colaligns, rowfmt): - "Return a string which represents a row of data cells." - if not rowfmt: - return None - if hasattr(rowfmt, "__call__"): - return rowfmt(padded_cells, colwidths, colaligns) - else: - return _build_simple_row(padded_cells, rowfmt) - - -def _append_basic_row(lines, padded_cells, colwidths, colaligns, rowfmt): - lines.append(_build_row(padded_cells, colwidths, colaligns, rowfmt)) - return lines - - -def _append_multiline_row( - lines, padded_multiline_cells, padded_widths, colaligns, rowfmt, pad -): - colwidths = [w - 2 * pad for w in padded_widths] - cells_lines = [c.splitlines() for c in padded_multiline_cells] - nlines = max(map(len, cells_lines)) # number of lines in the row - # vertically pad cells where some lines are missing - cells_lines = [ - (cl + [" " * w] * (nlines - len(cl))) for cl, w in zip(cells_lines, colwidths) - ] - lines_cells = [[cl[i] for cl in cells_lines] for i in range(nlines)] - for ln in lines_cells: - padded_ln = _pad_row(ln, pad) - _append_basic_row(lines, padded_ln, colwidths, colaligns, rowfmt) - return lines - - -def _build_line(colwidths, colaligns, linefmt): - "Return a string which represents a horizontal line." - if not linefmt: - return None - if hasattr(linefmt, "__call__"): - return linefmt(colwidths, colaligns) - else: - begin, fill, sep, end = linefmt - cells = [fill * w for w in colwidths] - return _build_simple_row(cells, (begin, sep, end)) - - -def _append_line(lines, colwidths, colaligns, linefmt): - lines.append(_build_line(colwidths, colaligns, linefmt)) - return lines - - -class JupyterHTMLStr(str): - """Wrap the string with a _repr_html_ method so that Jupyter - displays the HTML table""" - - def _repr_html_(self): - return self - - @property - def str(self): - """add a .str property so that the raw string is still accessible""" - return self - - -def _format_table(fmt, headers, rows, colwidths, colaligns, is_multiline): - """Produce a plain-text representation of the table.""" - lines = [] - hidden = fmt.with_header_hide if (headers and fmt.with_header_hide) else [] - pad = fmt.padding - headerrow = fmt.headerrow - - padded_widths = [(w + 2 * pad) for w in colwidths] - if is_multiline: - pad_row = lambda row, _: row # noqa do it later, in _append_multiline_row - append_row = partial(_append_multiline_row, pad=pad) - else: - pad_row = _pad_row - append_row = _append_basic_row - - padded_headers = pad_row(headers, pad) - padded_rows = [pad_row(row, pad) for row in rows] - - if fmt.lineabove and "lineabove" not in hidden: - _append_line(lines, padded_widths, colaligns, fmt.lineabove) - - if padded_headers: - append_row(lines, padded_headers, padded_widths, colaligns, headerrow) - if fmt.linebelowheader and "linebelowheader" not in hidden: - _append_line(lines, padded_widths, colaligns, fmt.linebelowheader) - - if padded_rows and fmt.linebetweenrows and "linebetweenrows" not in hidden: - # initial rows with a line below - for row in padded_rows[:-1]: - append_row(lines, row, padded_widths, colaligns, fmt.datarow) - _append_line(lines, padded_widths, colaligns, fmt.linebetweenrows) - # the last row without a line below - append_row(lines, padded_rows[-1], padded_widths, colaligns, fmt.datarow) - else: - for row in padded_rows: - append_row(lines, row, padded_widths, colaligns, fmt.datarow) - - if fmt.linebelow and "linebelow" not in hidden: - _append_line(lines, padded_widths, colaligns, fmt.linebelow) - - if headers or rows: - output = "\n".join(lines) - if fmt.lineabove == _html_begin_table_without_header: - return JupyterHTMLStr(output) - else: - return output - else: # a completely empty table - return "" - - -def _main(): - """\ - Usage: tabulate [options] [FILE ...] - - Pretty-print tabular data. - See also https://github.com/astanin/python-tabulate - - FILE a filename of the file with tabular data; - if "-" or missing, read data from stdin. - - Options: - - -h, --help show this message - -1, --header use the first row of data as a table header - -o FILE, --output FILE print table to FILE (default: stdout) - -s REGEXP, --sep REGEXP use a custom column separator (default: whitespace) - -F FPFMT, --float FPFMT floating point number format (default: g) - -f FMT, --format FMT set output table format; supported formats: - plain, simple, grid, fancy_grid, pipe, orgtbl, - rst, mediawiki, html, latex, latex_raw, - latex_booktabs, tsv - (default: simple) - """ - import getopt - import sys - import textwrap - - usage = textwrap.dedent(_main.__doc__) - try: - opts, args = getopt.getopt( - sys.argv[1:], - "h1o:s:F:A:f:", - ["help", "header", "output", "sep=", "float=", "align=", "format="], - ) - except getopt.GetoptError as e: - print(e) - print(usage) - sys.exit(2) - headers = [] - floatfmt = _DEFAULT_FLOATFMT - colalign = None - tablefmt = "simple" - sep = r"\s+" - outfile = "-" - for opt, value in opts: - if opt in ["-1", "--header"]: - headers = "firstrow" - elif opt in ["-o", "--output"]: - outfile = value - elif opt in ["-F", "--float"]: - floatfmt = value - elif opt in ["-C", "--colalign"]: - colalign = value.split() - elif opt in ["-f", "--format"]: - if value not in tabulate_formats: - print("%s is not a supported table format" % value) - print(usage) - sys.exit(3) - tablefmt = value - elif opt in ["-s", "--sep"]: - sep = value - elif opt in ["-h", "--help"]: - print(usage) - sys.exit(0) - files = [sys.stdin] if not args else args - with (sys.stdout if outfile == "-" else open(outfile, "w")) as out: - for f in files: - if f == "-": - f = sys.stdin - if _is_file(f): - _pprint_file( - f, - headers=headers, - tablefmt=tablefmt, - sep=sep, - floatfmt=floatfmt, - file=out, - colalign=colalign, - ) - else: - with open(f) as fobj: - _pprint_file( - fobj, - headers=headers, - tablefmt=tablefmt, - sep=sep, - floatfmt=floatfmt, - file=out, - colalign=colalign, - ) - - -def _pprint_file(fobject, headers, tablefmt, sep, floatfmt, file, colalign): - rows = fobject.readlines() - table = [re.split(sep, r.rstrip()) for r in rows if r.strip()] - print( - tabulate(table, headers, tablefmt, floatfmt=floatfmt, colalign=colalign), - file=file, - ) - - -if __name__ == "__main__": - _main() From 7787dab5129e307477b2da7e7af416a4ed8d86e9 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Thu, 18 Aug 2022 13:58:04 +0300 Subject: [PATCH 030/364] fix: jans-cli unfinished create new client --- jans-cli-tui/cli/config_cli.py | 33 ++++++++---- jans-cli-tui/models/oauth.py | 95 ++++++++++++++++++++-------------- jans-cli-tui/wrapper_test.py | 39 +++++++++++++- 3 files changed, 117 insertions(+), 50 deletions(-) diff --git a/jans-cli-tui/cli/config_cli.py b/jans-cli-tui/cli/config_cli.py index bfe759f3190..59f88694608 100755 --- a/jans-cli-tui/cli/config_cli.py +++ b/jans-cli-tui/cli/config_cli.py @@ -583,22 +583,23 @@ def print_exception(self, e): if hasattr(e, 'body'): try: jsdata = json.loads(e.body.decode()) - print(self.colored_text(e.body.decode(), error_color)) + self.raise_error(e.body.decode()) error_printed = True except: pass if not error_printed: - print(self.colored_text("Error retreiving data", warning_color)) - print('\u001b[38;5;196m') + msg = "Error retreiving data: " + err = 'None' if isinstance(e, str): - print(e) + err = e if hasattr(e, 'reason'): - print(e.reason) + err = e.reason if hasattr(e, 'body'): - print(e.body) + err = e.body if hasattr(e, 'args'): - print(', '.join(e.args)) - print('\u001b[0m') + err = ', '.join(e.args) + + self.raise_error(msg + str(err)) def colored_text(self, text, color=255): return u"\u001b[38;5;{}m{}\u001b[0m".format(color, text) @@ -934,6 +935,10 @@ def get_requests(self, endpoint, params={}): cert=self.mtls_client_cert ) self.log_response(response) + + if self.wrapped: + return response + if response.status_code in (404, 401): print(self.colored_text("Server returned {}".format(response.status_code), error_color)) print(self.colored_text(response.text, error_color)) @@ -988,6 +993,9 @@ def get_schema_from_reference(self, ref): return schema_ + def get_mime_for_endpoint(self, endpoint, req='requestBody'): + for key in endpoint.info[req]['content']: + return key def post_requests(self, endpoint, data): url = 'https://{}{}'.format(self.host, endpoint.path) @@ -1003,12 +1011,16 @@ def post_requests(self, endpoint, data): verify=self.verify_ssl, cert=self.mtls_client_cert ) + self.log_response(response) + if self.wrapped: + return response + try: return response.json() except: - self.print_exception(response.text) + print(response.text) def delete_requests(self, endpoint, url_param_dict): @@ -1231,6 +1243,9 @@ def process_command_post(self, path, suffix_param, endpoint_params, data_fn, dat elif path['__method__'] == 'put': response = self.put_requests(endpoint, data) + if self.wrapped: + return response + self.print_response(response) def process_command_put(self, path, suffix_param, endpoint_params, data_fn, data=None): diff --git a/jans-cli-tui/models/oauth.py b/jans-cli-tui/models/oauth.py index 2a4f87cdf94..b51289e24bb 100644 --- a/jans-cli-tui/models/oauth.py +++ b/jans-cli-tui/models/oauth.py @@ -115,46 +115,57 @@ def oauth_set_center_frame(self): self.center_container = self.oauth_main_container def oauth_update_clients(self): - try : - result = self.cli_object.process_command_by_id('get-oauth-openid-clients', '', 'limit:10', {}) - - - data =[] - for d in result: - data.append( - [ - d['inum'], - d['clientName']['values'][''], - ','.join(d['grantTypes']), - d['subjectType'] - ] - ) - - clients = JansVerticalNav( - myparent=self, - headers=['Client ID', 'Client Name', 'Grant Types', 'Subject Type'], - preferred_size= [0,0,30,0], - data=data, - on_enter=self.edit_client_dialog, - on_display=self.data_display_dialog, - # selection_changed=self.data_selection_changed, - selectes=0, - headerColor='green', - entriesColor='white', - all_data=result + try : + rsponse = self.cli_object.process_command_by_id('get-oauth-openid-clients', '', 'limit:10', {}) + except Exception as e: + self.show_message("Error getting clients", str(e)) + return + + if rsponse.status_code not in (200, 201): + self.show_message("Error getting clients", str(rsponse.text)) + return + + try: + result = rsponse.json() + except Exception: + self.show_message("Error getting clients", str(rsponse.text)) + return + + + data =[] + + for d in result: + data.append( + [ + d['inum'], + d.get('clientName', {}).get('values', {}).get('', ''), + ','.join(d.get('grantTypes', [])), + d.get('subjectType', '') + ] ) - self.layout.focus(clients) # clients.focuse..!? TODO >> DONE - self.oauth_data_container['clients'] = HSplit([ - clients - ]) + clients = JansVerticalNav( + myparent=self, + headers=['Client ID', 'Client Name', 'Grant Types', 'Subject Type'], + preferred_size= [0,0,30,0], + data=data, + on_enter=self.edit_client_dialog, + on_display=self.data_display_dialog, + # selection_changed=self.data_selection_changed, + selectes=0, + headerColor='green', + entriesColor='white', + all_data=result + ) - get_app().invalidate() + self.layout.focus(clients) # clients.focuse..!? TODO >> DONE + self.oauth_data_container['clients'] = HSplit([ + clients + ]) + + get_app().invalidate() - except Exception as e: - self.oauth_data_container['clients'] = HSplit([Label("Faild to Fitch client Data.. Reason: " + str(e))], width=D()) - get_app().invalidate() def oauth_get_clients(self): self.oauth_data_container['clients'] = HSplit([Label("Please wait while getting clients")], width=D()) @@ -164,9 +175,7 @@ def oauth_get_clients(self): def update_oauth_scopes(self, start_index=0): try : result = self.cli_object.process_command_by_id('get-oauth-scopes', '', 'limit:10', {}) - data =[] - for d in result: data.append( [ @@ -248,8 +257,16 @@ def save_client(self, dialog): data[list_key] = data[list_key].splitlines() self.logger.debug(str(data)) - - return False + response = self.cli_object.process_command_by_id( + operation_id='post-oauth-openid-clients', + url_suffix='', + endpoint_args='', + data_fn='', + data=data + ) + if response.status_code in (200, 201): + self.oauth_get_clients() + return True def add_client(self): dialog = EditClientDialog(self, title="Add Client", data={}, save_handler=self.save_client) diff --git a/jans-cli-tui/wrapper_test.py b/jans-cli-tui/wrapper_test.py index 8d1488d5e0c..803f4ef33b8 100644 --- a/jans-cli-tui/wrapper_test.py +++ b/jans-cli-tui/wrapper_test.py @@ -1,5 +1,8 @@ +import os +import json from cli import config_cli test_client = config_cli.client_id if config_cli.test_client else None +config_cli.debug = True cli_object = config_cli.JCA_CLI( host=config_cli.host, client_id=config_cli.client_id, @@ -8,11 +11,11 @@ test_client=test_client ) -print(config_cli.host, config_cli.client_id, config_cli.client_secret, config_cli.access_token, cli_object.use_test_client) +#print(config_cli.host, config_cli.client_id, config_cli.client_secret, config_cli.access_token, cli_object.use_test_client) status = cli_object.check_connection() print(status) - +""" response = cli_object.get_device_verification_code() result = response.json() @@ -22,4 +25,36 @@ cli_object.get_jwt_access_token(result) +print(result) +""" + +client_data = { + "displayName": "Test 3", + "clientSecret": "TopSecret", + "redirectUris": ["https://www.jans.io/cb"], + "applicationType": "web", + "grantTypes": ["implicit", "refresh_token"] +} +""" +result = cli_object.process_command_by_id( + operation_id='post-oauth-openid-clients', + url_suffix='', + endpoint_args='', + data_fn='', + data=client_data + ) +""" + +result = cli_object.process_command_by_id( + operation_id='delete-oauth-openid-clients-by-inum', + url_suffix='inum:7710112a-ce34-445b-8d85-fd18bec56ce5', + endpoint_args='', + data_fn='', + data={} + ) + + + + + print(result) From e61a10c9dca576ae6639b442221133485efdcffd Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Thu, 18 Aug 2022 16:45:39 +0300 Subject: [PATCH 031/364] feat: jans-cli implement delete client --- jans-cli-tui/jans-cli-tui.py | 12 ++++++--- jans-cli-tui/models/oauth.py | 26 +++++++++++++++++++ .../wui_components/jans_cli_dialog.py | 2 ++ .../wui_components/jans_vetrical_nav.py | 16 +++++++++--- 4 files changed, 50 insertions(+), 6 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index 130fd3c28a0..037cadcafce 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -22,14 +22,15 @@ HSplit, VSplit, VerticalAlign, + HorizontalAlign, DynamicContainer, FloatContainer, - Window + Window, + FormattedTextControl ) -from prompt_toolkit.layout.containers import VerticalAlign from prompt_toolkit.layout.dimension import D from prompt_toolkit.layout.layout import Layout -from prompt_toolkit.lexers import PygmentsLexer ,DynamicLexer +from prompt_toolkit.lexers import PygmentsLexer, DynamicLexer from prompt_toolkit.widgets import ( Box, Button, @@ -507,6 +508,11 @@ def show_again(self): ## nasted dialog Button self.show_message("Again", "Nasted Dialogs",) + def get_confirm_dialog(self, message): + body = VSplit([Label(message)], align=HorizontalAlign.CENTER) + buttons = [Button("No"), Button("Yes")] + dialog = JansGDialog(self, title="Confirmation", body=body, buttons=buttons) + return dialog application = JansCliApp() diff --git a/jans-cli-tui/models/oauth.py b/jans-cli-tui/models/oauth.py index b51289e24bb..51b5fc3961f 100644 --- a/jans-cli-tui/models/oauth.py +++ b/jans-cli-tui/models/oauth.py @@ -152,6 +152,7 @@ def oauth_update_clients(self): data=data, on_enter=self.edit_client_dialog, on_display=self.data_display_dialog, + on_delete=self.delete_client, # selection_changed=self.data_selection_changed, selectes=0, headerColor='green', @@ -272,4 +273,29 @@ def add_client(self): dialog = EditClientDialog(self, title="Add Client", data={}, save_handler=self.save_client) result = self.show_jans_dialog(dialog) + def delete_client(self, selected, event): + + dialog = self.get_confirm_dialog("Are you sure want to delete client inum:\n {} ?".format(selected[0])) + + async def coroutine(): + app = get_app() + focused_before = app.layout.current_window + self.layout.focus(dialog) + result = await self.show_dialog_as_float(dialog) + try: + app.layout.focus(focused_before) + except: + app.layout.focus(self.center_frame) + + if result.lower() == 'yes': + result = self.cli_object.process_command_by_id( + operation_id='delete-oauth-openid-clients-by-inum', + url_suffix='inum:{}'.format(selected[0]), + endpoint_args='', + data_fn='', + data={} + ) + self.oauth_get_clients() + return result + ensure_future(coroutine()) diff --git a/jans-cli-tui/wui_components/jans_cli_dialog.py b/jans-cli-tui/wui_components/jans_cli_dialog.py index 369701f5733..0409df0b7d5 100644 --- a/jans-cli-tui/wui_components/jans_cli_dialog.py +++ b/jans-cli-tui/wui_components/jans_cli_dialog.py @@ -7,6 +7,8 @@ from prompt_toolkit.layout.dimension import D from static import DialogResult from functools import partial + + class JansGDialog: def __init__(self, parent, title, body, buttons=[]): self.future = Future() diff --git a/jans-cli-tui/wui_components/jans_vetrical_nav.py b/jans-cli-tui/wui_components/jans_vetrical_nav.py index 56dc4fe60e6..30b4a273c77 100644 --- a/jans-cli-tui/wui_components/jans_vetrical_nav.py +++ b/jans-cli-tui/wui_components/jans_vetrical_nav.py @@ -13,8 +13,8 @@ class JansVerticalNav(): - def __init__(self, myparent, headers, selectes, on_enter, on_display, - all_data=None, preferred_size=[],data=None, headerColor='green', entriesColor='white'): + def __init__(self, myparent, headers, selectes, on_enter, on_display, on_delete=None, + all_data=None, preferred_size=[], data=None, headerColor='green', entriesColor='white'): self.myparent = myparent # ListBox parent class self.headers = headers # ListBox headers @@ -25,6 +25,7 @@ def __init__(self, myparent, headers, selectes, on_enter, on_display, self.entriesColor = entriesColor self.spaces = [] self.on_enter = on_enter + self.on_delete = on_delete self.on_display = on_display self.mod_data = data self.all_data=all_data @@ -176,7 +177,7 @@ def _(event): self.on_enter(passed=passed,event=event,size=size,data=self.all_data[self.selectes]) - @kb.add("d") + @kb.add("j") def _(event): selected_line = [i.strip() for i in self.data[self.selectes]] size = self.myparent.output.get_size() @@ -187,6 +188,15 @@ def _(event): size=size, data=self.all_data[self.selectes]) + + @kb.add("d") + def _(event): + if self.on_delete: + selected_line = [i.strip() for i in self.data[self.selectes]] + self.on_delete( + selected=selected_line, + event=event) + return kb # -------------------------------------------------------------------------------- # From 11cb99329c827a51a9e5bcdbb8120ba4a7e88b25 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Thu, 18 Aug 2022 07:40:08 -0700 Subject: [PATCH 032/364] feat: jans-cli add tabs and help --- jans-cli-tui/jans-cli-tui.py | 22 ++- jans-cli-tui/models/oauth.py | 27 +++- .../wui_components/edit_client_dialog.py | 148 +++++++++++++++++- .../wui_components/jans_vetrical_nav.py | 17 +- 4 files changed, 202 insertions(+), 12 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index 130fd3c28a0..b1a651de6f5 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -23,9 +23,11 @@ VSplit, VerticalAlign, DynamicContainer, + HorizontalAlign, FloatContainer, - Window -) + Window, + FormattedTextControl + ) from prompt_toolkit.layout.containers import VerticalAlign from prompt_toolkit.layout.dimension import D from prompt_toolkit.layout.layout import Layout @@ -89,6 +91,7 @@ def do_exit(*c): get_app().exit(result=False) + class JansCliApp(Application, JansAuthServer): def __init__(self): @@ -290,9 +293,12 @@ def set_keybindings(self): self.bindings.add("tab")(self.focus_next) self.bindings.add("s-tab")(self.focus_previous) self.bindings.add("c-c")(do_exit) + self.bindings.add("f1")(self.help) # ----------------------------------------------------------------- # - + def help(self,ev): + self.show_message("Help",''' Edit current selection\n Display current item in JSON format\n Delete current selection''') + def handle_long_string (self,text,values,cb): lines = [] if len(text) > 20 : @@ -350,7 +356,9 @@ def handle_long_string (self,text,values,cb): def getTitledText(self, title, name, value='', height=1, jans_help='', width=None,style=''): title += ': ' multiline = height > 1 - ta = TextArea(text=value, multiline=multiline,style="class:titledtext") + ## I added Str() because the value sometime is int like > Acess token life time + ## Error is raised + ta = TextArea(text=str(value), multiline=multiline,style="class:titledtext") ta.window.jans_name = name ta.window.jans_help = jans_help ta.window.me = ta @@ -506,7 +514,11 @@ def show_message(self, title, message, buttons=[]): def show_again(self): ## nasted dialog Button self.show_message("Again", "Nasted Dialogs",) - + def get_confirm_dialog(self, message): + body = VSplit([Label(message)], align=HorizontalAlign.CENTER) + buttons = [Button("No"), Button("Yes")] + dialog = JansGDialog(self, title="Confirmation", body=body, buttons=buttons) + return dialog application = JansCliApp() diff --git a/jans-cli-tui/models/oauth.py b/jans-cli-tui/models/oauth.py index b51289e24bb..37696e3f0f3 100644 --- a/jans-cli-tui/models/oauth.py +++ b/jans-cli-tui/models/oauth.py @@ -152,7 +152,7 @@ def oauth_update_clients(self): data=data, on_enter=self.edit_client_dialog, on_display=self.data_display_dialog, - # selection_changed=self.data_selection_changed, + on_delete=self.delete_client, selectes=0, headerColor='green', entriesColor='white', @@ -272,4 +272,29 @@ def add_client(self): dialog = EditClientDialog(self, title="Add Client", data={}, save_handler=self.save_client) result = self.show_jans_dialog(dialog) + def delete_client(self, selected, event): + + dialog = self.get_confirm_dialog("Are you sure want to delete client inum:\n {} ?".format(selected[0])) + + async def coroutine(): + app = get_app() + focused_before = app.layout.current_window + self.layout.focus(dialog) + result = await self.show_dialog_as_float(dialog) + try: + app.layout.focus(focused_before) + except: + app.layout.focus(self.center_frame) + + if result.lower() == 'yes': + result = self.cli_object.process_command_by_id( + operation_id='delete-oauth-openid-clients-by-inum', + url_suffix='inum:{}'.format(selected[0]), + endpoint_args='', + data_fn='', + data={} + ) + self.oauth_get_clients() + return result + ensure_future(coroutine()) diff --git a/jans-cli-tui/wui_components/edit_client_dialog.py b/jans-cli-tui/wui_components/edit_client_dialog.py index 4e3a30ee37d..24c35c4093d 100644 --- a/jans-cli-tui/wui_components/edit_client_dialog.py +++ b/jans-cli-tui/wui_components/edit_client_dialog.py @@ -92,18 +92,162 @@ def prepare_tabs(self): self.myparent.getTitledText("Description", name='description', value=self.data.get('description',''), style='green'), Label(text="dropdown1",style='blue'), self.myparent.getTitledRadioButton("Subject Type", name='subjectType', values=[('public', 'Public'),('pairwise', 'Pairwise')], current_value=self.data.get('subjectType'), style='green'), - self.myparent.getTitledCheckBoxList("Grant", name='grantTypes', values=[('authorization_code', 'Authorizatuin Code'), ('refresh_token', 'Refresh Token'), ('urn:ietf:params:oauth:grant-type:uma-ticket', 'UMA Ticket'), ('client_credentials', 'Client Credentials'), ('password', 'Password'), ('implicit', 'Implicit')], current_values=self.data.get('grantTypes', []), style='green'), + self.myparent.getTitledCheckBoxList("Grant", name='grantTypes', values=[('authorization_code', 'Authorization Code'), ('refresh_token', 'Refresh Token'), ('urn:ietf:params:oauth:grant-type:uma-ticket', 'UMA Ticket'), ('client_credentials', 'Client Credentials'), ('password', 'Password'), ('implicit', 'Implicit')], current_values=self.data.get('grantTypes', []), style='green'), self.myparent.getTitledCheckBoxList("Response Types", name='responseTypes', values=['code', 'token', 'id_token'], current_values=self.data.get('responseTypes', []), style='green'), self.myparent.getTitledCheckBox("Supress Authorization", name='dynamicRegistrationPersistClientAuthorizations', checked=self.data.get('dynamicRegistrationPersistClientAuthorizations'), style='green'), self.myparent.getTitledRadioButton("Application Type", name='applicationType', values=['native','web'], current_value=self.data.get('applicationType'), style='green'), self.myparent.getTitledText("Redirect Uris", name='redirectUris', value='\n'.join(self.data.get('redirectUris', [])), height=3, style='green'), - #self.myparent.getTitledText("Redirect Regex", name='redirectregex', value=self.data.get('redirectUris', ''), style='green'), #name not identified + self.myparent.getTitledText("Redirect Regex", name='redirectUrisRegex', value=self.data.get('redirectUrisRegex', ''), style='green'), self.myparent.getTitledText("Scopes", name='scopes', value='\n'.join(self.data.get('scopes', [])), height=3, style='green'), ],width=D(), ) + + self.tabs['Tokens'] = HSplit([ + self.myparent.getTitledRadioButton("Access Token Type", name='accessTokenAsJwt', values=['JWT','Reference'], current_value=self.data.get('accessTokenAsJwt'),style='green'), + self.myparent.getTitledCheckBox("Incliude Claims in id_token", name='includeClaimsInIdToken', checked=self.data.get('includeClaimsInIdToken'),style='green'), + self.myparent.getTitledCheckBox("Run introspection script before JWT access token creation", name='runIntrospectionScriptBeforeAccessTokenAsJwtCreationAndIncludeClaims', checked=self.data.get('runIntrospectionScriptBeforeAccessTokenAsJwtCreationAndIncludeClaims'),style='green'), + self.myparent.getTitledText(title="Token binding confirmation method for id_token", name='idTokenTokenBindingCnf',value=self.data.get('idTokenTokenBindingCnf',''),style='green'), + self.myparent.getTitledText(title="Access token additional audiences", name='additionalAudience',value=self.data.get('additionalAudience',''),style='green'), + VSplit([ + Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3) + ]), + self.myparent.getTitledText("Access token lifetime", name='accessTokenLifetime', value=self.data.get('accessTokenLifetime',''),style='green'), + self.myparent.getTitledText("Refresh token lifetime", name='refreshTokenLifetime', value=self.data.get('refreshTokenLifetime',''),style='green'), + self.myparent.getTitledText("Defult max authn age", name='defaultMaxAge', value=self.data.get('defaultMaxAge',''),style='green'), + ],width=D()) + self.tabs['Logout'] = HSplit([ + + self.myparent.getTitledText("Front channel logout URI", name='frontChannelLogoutUri', value=self.data.get('frontChannelLogoutUri',''),style='green'), + self.myparent.getTitledText("Post logout redirect URI", name='postLogoutRedirectUris', value=self.data.get('postLogoutRedirectUris',''),style='green'), + self.myparent.getTitledText("Back channel logout URI", name='backchannelLogoutUri', value=self.data.get('backchannelLogoutUri',''),style='green'), + self.myparent.getTitledCheckBox("Back channel logout session required", name='backchannelLogoutSessionRequired', checked=self.data.get('backchannelLogoutSessionRequired'),style='green'), + self.myparent.getTitledCheckBox("Front channel logout session required", name='frontChannelLogoutSessionRequired', checked=self.data.get('frontChannelLogoutSessionRequired'),style='green'), + + ],width=D() + ) + + self.tabs['SoftwareInfo'] = HSplit([ + self.myparent.getTitledText(title ="Client URI", name='clientUri', value=self.data.get('clientUri',''),style='green'), + self.myparent.getTitledText(title ="Policy URI", name='policyUri', value=self.data.get('policyUri',''),style='green'), + self.myparent.getTitledText(title ="Logo URI", name='logoUri', value=self.data.get('logoUri',''),style='green'), + self.myparent.getTitledText(title ="Term of service URI", name='tosUri', value=self.data.get('tosUri',''),style='green'), + self.myparent.getTitledText(title ="Contacts", name='contacts', value=self.data.get('contacts',''),style='green'), + VSplit([ + Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3,) + ]), + self.myparent.getTitledText(title ="Authorized JS origins", name='authorizedOrigins', value=self.data.get('authorizedOrigins',''),style='green'), + VSplit([ + Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3) + ]), + self.myparent.getTitledText(title ="Software id", name='softwareId', value=self.data.get('softwareId',''),style='green'), + self.myparent.getTitledText(title ="Software version", name='softwareVersion', value=self.data.get('softwareVersion',''), style='green'), + self.myparent.getTitledText(title ="Software statement", name='softwareStatement', value=self.data.get('softwareStatement',''), style='green'), + + ],width=D()) + + self.tabs['CIBA/PAR/UMA'] = HSplit([ + Label(text="CIBA",style='bold'), + self.myparent.getTitledRadioButton("Token delivery method", name='backchannelTokenDeliveryMode', current_value=self.data.get('backchannelTokenDeliveryMode'), values=['poll','push', 'ping'],style='green'), + self.myparent.getTitledText(title ="Client notification endpoint", name='backchannelClientNotificationEndpoint', value=self.data.get('backchannelClientNotificationEndpoint',''),style='green'), + self.myparent.getTitledCheckBox("Require user code param", name='backchannelUserCodeParameterSupported', checked=self.data.get('backchannelUserCodeParameterSupported'),style='green'), + + Label(text="PAR",style='bold'), + + self.myparent.getTitledText(title ="Request lifetime", name='parLifetime', value=self.data.get('parLifetime',''),style='green'), + self.myparent.getTitledCheckBox("Request PAR", name='sessionIdRequestParameterEnabled', checked=self.data.get('sessionIdRequestParameterEnabled'),style='green'), + + Label("UMA",style='bold'), + self.myparent.getTitledRadioButton("PRT token type", name='applicationType!', values=['JWT', 'Reference'], current_value=self.data.get('applicationType!'),style='green'), + self.myparent.getTitledText(title ="Claims redirect URI", name='claimRedirectUris', value=self.data.get('claimRedirectUris',''),style='green'), + + Label(text="dropdown1",style='blue'), ## TODO with Jans VerticalNav + Label(text="dropdown2",style='blue'), ## TODO with Jans VerticalNav + Label(text="tabel",style='blue'), ## TODO with Jans VerticalNav + + ] + ) + + self.tabs['Encryption/Signing'] = HSplit([ + self.myparent.getTitledText(title ="Client JWKS URI", name='jwksUri', value=self.data.get('jwksUri',''),style='green'), + self.myparent.getTitledText(title ="Client JWKS", name='jwks', value=self.data.get('jwks',''),style='green'), + VSplit([ + Label(text="id_token"), + Label(text="a, b, c",style='red'), + ]), + VSplit([ + Label(text="Access token"), + Label(text="a",style='red'), + ]), + VSplit([ + Label(text="Userinfo"), + Label(text="a, b, c",style='red'), + ]), + VSplit([ + Label(text="JARM"), + Label(text="a, b, c",style='red'), + ]), + VSplit([ + Label(text="Request Object"), + Label(text="a, b, c",style='red'), + ]), + + ] + ) + + self.tabs['Advanced Client Properties'] = HSplit([ + + self.myparent.getTitledCheckBox("Default Prompt login", name='defaultPromptLogin', checked=self.data.get('defaultPromptLogin'),style='green'), + VSplit([ + self.myparent.getTitledCheckBox("Persist Authorizations", name='persistClientAuthorizations', checked=self.data.get('persistClientAuthorizations'),style='green'), + # self.myparent.getTitledCheckBox("Keep expired", name='Supress', checked=self.data.get('Supress'),style='green'), + ]) , + self.myparent.getTitledCheckBox("Allow spontaneos scopes", name='allowSpontaneousScopes', checked=self.data.get('allowSpontaneousScopes'),style='green'), + + self.myparent.getTitledText("spontaneos scopes validation regex", name='spontaneousScopes', value=self.data.get('spontaneousScopes',''),style='green'), + VSplit([ + Label(text="Spontaneous Scopes",style='green'), + Button("view current", handler=self.myparent.show_again,left_symbol='',right_symbol='',) + + ]) , + self.myparent.getTitledText("Initial Login URI", name='initiateLoginUri', value=self.data.get('initiateLoginUri',''),style='green'), + + VSplit([ + self.myparent.getTitledText("Request URIs", name='requestUris', value=self.data.get('requestUris',''),style='green'), + Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3,) + ]) , + + Label(text="Dropdown 3",style='blue'), + + VSplit([ + # self.myparent.getTitledText("Allowed ACRs", name='clientSecret', value=self.data.get('clientSecret',''),style='green'), + Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3,) + ]) , + self.myparent.getTitledText("TLS Subject DN", name='x5c', value=self.data.get('x5c',''),style='green'), + + VSplit([ + # self.myparent.getTitledCheckBox("Client Experiation Date", name='id_token_claims', checked=self.data.get('id_token_claims'),style='green'), + # attached to date + Label(text="Pick Date",style='blue'), + ]) , + + + + ],width=D() + ) + + self.tabs['Client Scripts'] = HSplit([ + Label(text="Dropdown 4",style='blue'), + Label(text="Dropdown 5",style='blue'), + Label(text="Dropdown 6",style='blue'), + Label(text="Dropdown 7",style='blue'), + Label(text="Dropdown 8",style='blue'), + ] + ) + + self.myparent.logger.debug("***************************************") self.left_nav = list(self.tabs.keys())[0] diff --git a/jans-cli-tui/wui_components/jans_vetrical_nav.py b/jans-cli-tui/wui_components/jans_vetrical_nav.py index 56dc4fe60e6..2cba0c5dc2e 100644 --- a/jans-cli-tui/wui_components/jans_vetrical_nav.py +++ b/jans-cli-tui/wui_components/jans_vetrical_nav.py @@ -13,8 +13,8 @@ class JansVerticalNav(): - def __init__(self, myparent, headers, selectes, on_enter, on_display, - all_data=None, preferred_size=[],data=None, headerColor='green', entriesColor='white'): + def __init__(self, myparent, headers, selectes, on_enter, on_display, on_delete=None, + all_data=None, preferred_size=[], data=None, headerColor='green', entriesColor='white'): self.myparent = myparent # ListBox parent class self.headers = headers # ListBox headers @@ -25,6 +25,7 @@ def __init__(self, myparent, headers, selectes, on_enter, on_display, self.entriesColor = entriesColor self.spaces = [] self.on_enter = on_enter + self.on_delete = on_delete self.on_display = on_display self.mod_data = data self.all_data=all_data @@ -176,7 +177,7 @@ def _(event): self.on_enter(passed=passed,event=event,size=size,data=self.all_data[self.selectes]) - @kb.add("d") + @kb.add("j") def _(event): selected_line = [i.strip() for i in self.data[self.selectes]] size = self.myparent.output.get_size() @@ -187,8 +188,16 @@ def _(event): size=size, data=self.all_data[self.selectes]) - return kb + @kb.add("d") + def _(event): + if self.on_delete: + selected_line = [i.strip() for i in self.data[self.selectes]] + self.on_delete( + selected=selected_line, + event=event) + + return kb # -------------------------------------------------------------------------------- # # -------------------------------------------------------------------------------- # From 6d2f7ba215e6261e690d769acbbf7855e1df572a Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Thu, 18 Aug 2022 18:29:11 +0300 Subject: [PATCH 033/364] fix: jans cli cleaning --- jans-cli-tui/wrapper_test.py | 11 ++++-- .../wui_components/jans_vetrical_nav.py | 36 ++++++------------- 2 files changed, 19 insertions(+), 28 deletions(-) diff --git a/jans-cli-tui/wrapper_test.py b/jans-cli-tui/wrapper_test.py index 803f4ef33b8..7cb8c519e79 100644 --- a/jans-cli-tui/wrapper_test.py +++ b/jans-cli-tui/wrapper_test.py @@ -43,7 +43,7 @@ data_fn='', data=client_data ) -""" + result = cli_object.process_command_by_id( operation_id='delete-oauth-openid-clients-by-inum', @@ -52,7 +52,14 @@ data_fn='', data={} ) - +""" +result = cli_object.process_command_by_id( + operation_id='get-oauth-openid-clients', + url_suffix='', + endpoint_args='', + data_fn='', + data={} + ) diff --git a/jans-cli-tui/wui_components/jans_vetrical_nav.py b/jans-cli-tui/wui_components/jans_vetrical_nav.py index 30b4a273c77..ddeef2e9910 100644 --- a/jans-cli-tui/wui_components/jans_vetrical_nav.py +++ b/jans-cli-tui/wui_components/jans_vetrical_nav.py @@ -1,9 +1,5 @@ from prompt_toolkit.widgets import TextArea -from prompt_toolkit.layout.containers import ( - HSplit, - Window, - FloatContainer, -) +from prompt_toolkit.layout.containers import HSplit, Window, FloatContainer from prompt_toolkit.layout.controls import FormattedTextControl from prompt_toolkit.layout.margins import ScrollbarMargin from prompt_toolkit.formatted_text import merge_formatted_text @@ -30,16 +26,14 @@ def __init__(self, myparent, headers, selectes, on_enter, on_display, on_delete= self.mod_data = data self.all_data=all_data self.view_data() - self.handle_Header_spaces() - self.handle_Data_spaces() + self.handle_header_spaces() + self.handle_data_spaces() self.create_window() def view_data(self): result = [] - # mod_data = [] for i, entry in enumerate(self.mod_data): ## entry = ['1800.6c5faa', 'Jans Config Api Client', 'authorization_code,refresh_...', 'Reference] mod_entry = [] - for col in range(len(entry)) : if self.preferred_size[col] == 0: mod_entry.append(entry[col]) @@ -54,16 +48,6 @@ def view_data(self): self.mod_data = result def create_window(self): - self.ClientID = TextArea(height=1, multiline=False) - self.ClientName = TextArea(height=1, multiline=False) - self.GrantType = TextArea(height=1, multiline=False) - self.AccessToken = TextArea(height=1, multiline=False) - self.Helper = TextArea(height=1, multiline=False, focusable=False) - - if self.data == None: - self.data = [['No Cliend ID', 'No Client Name', - 'No Grant Type', 'No Access Token']] - self.container = FloatContainer( content=HSplit([ @@ -99,23 +83,23 @@ def create_window(self): ], ) - def handle_Header_spaces(self): + def handle_header_spaces(self): datalen = [] for dataline in range(len(self.mod_data )): line = [] for i in self.mod_data[dataline]: line.append(len(i)) datalen.append(line) - dict = {} + tmp_dict = {} for num in range(len(datalen[0])): - dict[num] = [] + tmp_dict[num] = [] for k in range(len(datalen)): for i in range(len(datalen[k])): - dict[i].append(datalen[k][i]) + tmp_dict[i].append(datalen[k][i]) - for i in dict: - self.spaces.append(max(dict[i])) + for i in tmp_dict: + self.spaces.append(max(tmp_dict[i])) for i in range(len(self.spaces)): ## handle header collesion (when the headers length is greater that the tallest data index length) @@ -126,7 +110,7 @@ def handle_Header_spaces(self): self.spaces[-1] = self.myparent.output.get_size()[1] - sum(self.spaces) + sum(len(s) for s in self.headers) ## handle last head spaces (add space to the end of ter. width to remove the white line) # -------------------------------------------------------------------------------- # # -------------------------------------------------------------------------------- # - def handle_Data_spaces(self): + def handle_data_spaces(self): for i in range(len(self.mod_data)): for k in range(len(self.spaces)): if len(self.mod_data[i][k]) != self.spaces[k]: From d1a4ed631b9fdad97262576022b8a93ba14a581d Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Fri, 19 Aug 2022 22:32:56 +0300 Subject: [PATCH 034/364] feat: jans-cli dropdown widget --- jans-cli-tui/jans-cli-tui.py | 5 +- jans-cli-tui/models/oauth.py | 4 +- jans-cli-tui/wui_components/jans_drop_down.py | 106 ++++++++++++++++++ .../wui_components/jans_vetrical_nav.py | 2 +- 4 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 jans-cli-tui/wui_components/jans_drop_down.py diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index 037cadcafce..e74991ea1bd 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -11,11 +11,14 @@ from pynput.keyboard import Key, Controller import prompt_toolkit +from prompt_toolkit.layout.margins import ScrollbarMargin from prompt_toolkit.application import Application from prompt_toolkit.application.current import get_app from prompt_toolkit.key_binding import KeyBindings from prompt_toolkit.key_binding.bindings.focus import focus_next, focus_previous from prompt_toolkit.layout.containers import Float, HSplit, VSplit +from prompt_toolkit.formatted_text import HTML, merge_formatted_text + from prompt_toolkit.layout.containers import ( ConditionalContainer, Float, @@ -143,7 +146,7 @@ def __init__(self): HSplit([ Frame(self.nav_bar.nav_window), self.center_frame, - self.status_bar + self.status_bar, ], ), floats=[] diff --git a/jans-cli-tui/models/oauth.py b/jans-cli-tui/models/oauth.py index 51b5fc3961f..c4167f9b818 100644 --- a/jans-cli-tui/models/oauth.py +++ b/jans-cli-tui/models/oauth.py @@ -42,11 +42,11 @@ from wui_components.jans_dialog import JansDialog from wui_components.jans_dialog_with_nav import JansDialogWithNav from wui_components.edit_client_dialog import EditClientDialog +from wui_components.jans_drop_down import DropDownWidget class JansAuthServer: - def initialize(self): self.oauth_containers = {} self.oauth_prepare_navbar() @@ -79,7 +79,7 @@ def oauth_prepare_containers(self): VSplit([ self.getButton(text="Get Clients", name='oauth:clients:get', jans_help="Retreive first 10 OpenID Connect clients", handler=self.oauth_get_clients), self.getTitledText('Search', name='oauth:clients:search', jans_help='Press enter to perform search'), - self.getButton(text="Add Client", name='oauth:clients:add', jans_help="To add a new client press this button", handler=self.add_client) + self.getButton(text="Add Client", name='oauth:clients:add', jans_help="To add a new client press this button", handler=self.add_client), ], padding=3, width=D(), diff --git a/jans-cli-tui/wui_components/jans_drop_down.py b/jans-cli-tui/wui_components/jans_drop_down.py new file mode 100644 index 00000000000..415e2506522 --- /dev/null +++ b/jans-cli-tui/wui_components/jans_drop_down.py @@ -0,0 +1,106 @@ + +from prompt_toolkit.application.current import get_app +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.layout.containers import Float, HSplit, Window +from prompt_toolkit.layout.controls import FormattedTextControl +from prompt_toolkit.formatted_text import HTML, merge_formatted_text +from prompt_toolkit.layout.margins import ScrollbarMargin +from prompt_toolkit.key_binding.bindings.focus import focus_next + +class JansSelectBox: + def __init__(self, entries=[]): + self.entries = entries + self.selected_line = 0 + self.container =HSplit(children=[Window( + content=FormattedTextControl( + text=self._get_formatted_text, + focusable=True, + ), + height=len(entries), + cursorline=False, + width=15, + style="bg:#4D4D4D", + right_margins=[ScrollbarMargin(display_arrows=True),], + )]) + + def _get_formatted_text(self): + result = [] + for i, entry in enumerate(self.entries): + if i == self.selected_line: + result.append(HTML(''.format('#ADD8E6', entry[1]))) + else: + result.append(HTML('{}'.format(entry[1]))) + result.append("\n") + + return merge_formatted_text(result) + + + def up(self): + self.selected_line = (self.selected_line - 1) % len(self.entries) + + + def down(self): + self.selected_line = (self.selected_line + 1) % len(self.entries) + + def __pt_container__(self): + return self.container + + +class DropDownWidget: + def __init__(self, entries=[]): + self.entries = entries + self.text = "Enter to Select" + self.dropdown = True + self.window = Window( + content=FormattedTextControl( + text=self._get_text, + focusable=True, + key_bindings=self._get_key_bindings(), + ), height=5) + + self.select_box = JansSelectBox(self.entries) + self.select_box_float = Float(content=self.select_box, xcursor=True, ycursor=True) + + + def _get_text(self): + if get_app().layout.current_window is self.window: + return HTML('> <'.format('#00FF00', self.text)) + return '> {} <'.format(self.text) + + + def _get_key_bindings(self): + kb = KeyBindings() + + + def _focus_next(event): + focus_next(event) + + @kb.add("enter") + def _enter(event) -> None: + + if self.select_box_float not in get_app().layout.container.floats: + get_app().layout.container.floats.insert(0, self.select_box_float) + else: + self.text = self.select_box.entries[self.select_box.selected_line][1] + get_app().layout.container.floats.remove(self.select_box_float) + + @kb.add("up") + def _up(event): + self.select_box.up() + + @kb.add("down") + def _up(event): + self.select_box.down() + + @kb.add("escape") + @kb.add("tab") + def _(event): + if self.select_box_float in get_app().layout.container.floats: + get_app().layout.container.floats.remove(self.select_box_float) + + _focus_next(event) + + return kb + + def __pt_container__(self): + return self.window diff --git a/jans-cli-tui/wui_components/jans_vetrical_nav.py b/jans-cli-tui/wui_components/jans_vetrical_nav.py index ddeef2e9910..c2424730884 100644 --- a/jans-cli-tui/wui_components/jans_vetrical_nav.py +++ b/jans-cli-tui/wui_components/jans_vetrical_nav.py @@ -137,7 +137,7 @@ def _get_formatted_text(self): result.append([("[SetCursorPosition]", "")]) result.append(' '.join(entry)) - result.append("\n") + result.append("\n") return merge_formatted_text(result) From 868b4c6773b51b6100159fbe9c1366f0da33fc26 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Fri, 19 Aug 2022 13:52:03 -0700 Subject: [PATCH 035/364] feat: jans-cli add client tabs --- jans-cli-tui/jans-cli-tui.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index d27f27072df..bcc6b7dbc72 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -294,8 +294,12 @@ def set_keybindings(self): self.bindings.add("tab")(self.focus_next) self.bindings.add("s-tab")(self.focus_previous) self.bindings.add("c-c")(do_exit) + self.bindings.add("f1")(self.help) # ----------------------------------------------------------------- # + def help(self,ev): + self.show_message("Help",''' Edit current selection\n Display current item in JSON format\n Delete current selection''') + # ----------------------------------------------------------------- # def handle_long_string (self,text,values,cb): lines = [] From ca5a869db1a898a486da941e1fa5e5f151833533 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Fri, 19 Aug 2022 14:17:24 -0700 Subject: [PATCH 036/364] fix: jans-cli add dropbox and there names --- .../wui_components/edit_client_dialog.py | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/jans-cli-tui/wui_components/edit_client_dialog.py b/jans-cli-tui/wui_components/edit_client_dialog.py index 09264a5703c..4a6ae5d8374 100644 --- a/jans-cli-tui/wui_components/edit_client_dialog.py +++ b/jans-cli-tui/wui_components/edit_client_dialog.py @@ -89,7 +89,7 @@ def prepare_tabs(self): self.myparent.getTitledText("Client Name", name='displayName', value=self.data.get('displayName',''), style='green'), self.myparent.getTitledText("Client Secret", name='clientSecret', value=self.data.get('clientSecret',''), style='green'), self.myparent.getTitledText("Description", name='description', value=self.data.get('description',''), style='green'), - Label(text="dropdown1",style='blue'), + DropDownWidget(entries=[('client_secret_basic','client_secret_basic'),('client_secret_post','client_secret_post'),('client_secret_jwt','client_secret_jwt'),('private_key_jwt','private_key_jwt')]), self.myparent.getTitledRadioButton("Subject Type", name='subjectType', values=[('public', 'Public'),('pairwise', 'Pairwise')], current_value=self.data.get('subjectType'), style='green'), self.myparent.getTitledCheckBoxList("Grant", name='grantTypes', values=[('authorization_code', 'Authorization Code'), ('refresh_token', 'Refresh Token'), ('urn:ietf:params:oauth:grant-type:uma-ticket', 'UMA Ticket'), ('client_credentials', 'Client Credentials'), ('password', 'Password'), ('implicit', 'Implicit')], current_values=self.data.get('grantTypes', []), style='green'), self.myparent.getTitledCheckBoxList("Response Types", name='responseTypes', values=['code', 'token', 'id_token'], current_values=self.data.get('responseTypes', []), style='green'), @@ -162,10 +162,9 @@ def prepare_tabs(self): self.myparent.getTitledRadioButton("PRT token type", name='applicationType!', values=['JWT', 'Reference'], current_value=self.data.get('applicationType!'),style='green'), self.myparent.getTitledText(title ="Claims redirect URI", name='claimRedirectUris', value=self.data.get('claimRedirectUris',''),style='green'), - DropDownWidget(entries=['Item1','Item2','Item3','Item4']), - DropDownWidget(entries=['Item11','Item22','Item33','Item44']), - # Label(text="dropdown1",style='blue'), ## TODO with Jans VerticalNav - # Label(text="dropdown2",style='blue'), ## TODO with Jans VerticalNav + DropDownWidget(entries=[('rptClaimsScripts','rptClaimsScripts')]), + DropDownWidget(entries=[('claimRedirectUris','claimRedirectUris')]), + Label(text="tabel",style='blue'), ## TODO with Jans VerticalNav ] @@ -233,22 +232,21 @@ def prepare_tabs(self): # attached to date Label(text="Pick Date",style='blue'), ]) , - - - + ],width=D() ) self.tabs['Client Scripts'] = HSplit([ - Label(text="Dropdown 4",style='blue'), - Label(text="Dropdown 5",style='blue'), - Label(text="Dropdown 6",style='blue'), - Label(text="Dropdown 7",style='blue'), - Label(text="Dropdown 8",style='blue'), + DropDownWidget(entries=[('spontaneousScopes','spontaneousScopes')]), + DropDownWidget(entries=[('updateTokenScriptDns','updateTokenScriptDns')]), + DropDownWidget(entries=[('postAuthnScripts','postAuthnScripts')]), + DropDownWidget(entries=[('introspectionScripts','introspectionScripts')]), + DropDownWidget(entries=[('dynamicRegistrationAllowedPasswordGrantScopes','dynamicRegistrationAllowedPasswordGrantScopes')]), + DropDownWidget(entries=[('consentGatheringScripts','consentGatheringScripts')]), + ] ) - self.myparent.logger.debug("***************************************") self.left_nav = list(self.tabs.keys())[0] From e0416af2194f78ceffb9a78fdd5011599ad2c75e Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Fri, 19 Aug 2022 14:18:02 -0700 Subject: [PATCH 037/364] fix: jans-cli add bigger size and title first selection --- jans-cli-tui/wui_components/jans_drop_down.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/jans-cli-tui/wui_components/jans_drop_down.py b/jans-cli-tui/wui_components/jans_drop_down.py index 415e2506522..f3997e6ac92 100644 --- a/jans-cli-tui/wui_components/jans_drop_down.py +++ b/jans-cli-tui/wui_components/jans_drop_down.py @@ -6,21 +6,24 @@ from prompt_toolkit.formatted_text import HTML, merge_formatted_text from prompt_toolkit.layout.margins import ScrollbarMargin from prompt_toolkit.key_binding.bindings.focus import focus_next +from prompt_toolkit.layout.dimension import D class JansSelectBox: def __init__(self, entries=[]): self.entries = entries self.selected_line = 0 - self.container =HSplit(children=[Window( + self.container =HSplit(children=[ Window( content=FormattedTextControl( text=self._get_formatted_text, focusable=True, + ), height=len(entries), cursorline=False, - width=15, + width=D(), #15, style="bg:#4D4D4D", right_margins=[ScrollbarMargin(display_arrows=True),], + wrap_lines=True )]) def _get_formatted_text(self): @@ -49,14 +52,19 @@ def __pt_container__(self): class DropDownWidget: def __init__(self, entries=[]): self.entries = entries - self.text = "Enter to Select" + if self.entries: ## should be replaced with the selected from data. + self.text = self.entries[0][1] + else: + self.text = "Enter to Select" + + self.dropdown = True self.window = Window( content=FormattedTextControl( text=self._get_text, focusable=True, key_bindings=self._get_key_bindings(), - ), height=5) + ), height=D()) #5 ## large sized enties get >> (window too small) self.select_box = JansSelectBox(self.entries) self.select_box_float = Float(content=self.select_box, xcursor=True, ycursor=True) From 1ca6016937da3e6415e6a4de7fa312fda611d178 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Sat, 20 Aug 2022 10:38:16 -0700 Subject: [PATCH 038/364] fix jans-cli fix position and scrolling in dropdown --- jans-cli-tui/models/oauth.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jans-cli-tui/models/oauth.py b/jans-cli-tui/models/oauth.py index baab98cbae2..587d5b475e0 100644 --- a/jans-cli-tui/models/oauth.py +++ b/jans-cli-tui/models/oauth.py @@ -43,7 +43,7 @@ from wui_components.jans_dialog_with_nav import JansDialogWithNav from wui_components.edit_client_dialog import EditClientDialog from wui_components.jans_drop_down import DropDownWidget - +from wui_components.jans_data_picker import DateSelectWidget class JansAuthServer: @@ -67,7 +67,7 @@ def oauth_prepare_containers(self): VSplit([ self.getButton(text="Get Scopes", name='oauth:scopes:get', jans_help="Retreive first 10 Scopes", handler=self.oauth_get_scopes), self.getTitledText('Search: ', name='oauth:scopes:search', jans_help='Press enter to perform search'), - self.getButton(text="Add Scope", name='oauth:scopes:add', jans_help="To add a new scope press this button") + self.getButton(text="Add Scope", name='oauth:scopes:add', jans_help="To add a new scope press this button"), ], padding=3, width=D(), From ff300d09e64a14739cce1edface02c9b841b35eb Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Sat, 20 Aug 2022 10:38:34 -0700 Subject: [PATCH 039/364] fix jans-cli fix position and scrolling in dropdown --- jans-cli-tui/wui_components/jans_drop_down.py | 49 ++++++++++++++++--- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/jans-cli-tui/wui_components/jans_drop_down.py b/jans-cli-tui/wui_components/jans_drop_down.py index f3997e6ac92..109b92b6e63 100644 --- a/jans-cli-tui/wui_components/jans_drop_down.py +++ b/jans-cli-tui/wui_components/jans_drop_down.py @@ -9,21 +9,31 @@ from prompt_toolkit.layout.dimension import D class JansSelectBox: - def __init__(self, entries=[]): + def __init__(self, entries=[],height=4,rotatable_up=True,rotatable_down=True): self.entries = entries self.selected_line = 0 + # --------------------------------------------------- # + self.height=min(len(self.entries),height) + self.rotatable_up = rotatable_up + self.rotatable_down = rotatable_down + self.firstValue = self.entries[0] + self.lastValue = self.entries[-1] + # --------------------------------------------------- # + self.container =HSplit(children=[ Window( content=FormattedTextControl( text=self._get_formatted_text, focusable=True, - ), - height=len(entries), + height=self.height, cursorline=False, width=D(), #15, style="bg:#4D4D4D", right_margins=[ScrollbarMargin(display_arrows=True),], - wrap_lines=True + wrap_lines=True, + allow_scroll_beyond_bottom=True, + + )]) def _get_formatted_text(self): @@ -38,12 +48,35 @@ def _get_formatted_text(self): return merge_formatted_text(result) + def shift(self,seq, n): + return seq[n:]+seq[:n] + def up(self): - self.selected_line = (self.selected_line - 1) % len(self.entries) + if self.selected_line == 0 : + if self.rotatable_up and self.entries[self.selected_line] == self.firstValue: + pass + else : + self.entries = self.shift(self.entries,-1) + else : + self.selected_line = (self.selected_line - 1) % (self.height) def down(self): - self.selected_line = (self.selected_line + 1) % len(self.entries) + + if self.selected_line +1 == (self.height) : + if self.rotatable_down and self.entries[self.selected_line] == self.lastValue: + pass + else: + self.entries = self.shift(self.entries,1) + else : + self.selected_line = (self.selected_line + 1) % (self.height) + + ### to jump the holl height + # if self.selected_line +1 < (self.height) : + # self.selected_line = (self.selected_line + 1) % (self.height) + # elif self.selected_line +1 == (self.height) : + # self.entries = self.shift(self.entries,self.height) + # self.selected_line = (self.selected_line + 1) % (self.height) def __pt_container__(self): return self.container @@ -66,7 +99,7 @@ def __init__(self, entries=[]): key_bindings=self._get_key_bindings(), ), height=D()) #5 ## large sized enties get >> (window too small) - self.select_box = JansSelectBox(self.entries) + self.select_box = JansSelectBox(entries=self.entries,rotatable_down=True,rotatable_up=True,height=4) self.select_box_float = Float(content=self.select_box, xcursor=True, ycursor=True) @@ -87,7 +120,7 @@ def _focus_next(event): def _enter(event) -> None: if self.select_box_float not in get_app().layout.container.floats: - get_app().layout.container.floats.insert(0, self.select_box_float) + get_app().layout.container.floats.append(self.select_box_float) else: self.text = self.select_box.entries[self.select_box.selected_line][1] get_app().layout.container.floats.remove(self.select_box_float) From 5640f4fec361defd4fb67e30309cbac7205db327 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Mon, 22 Aug 2022 13:39:28 +0300 Subject: [PATCH 040/364] feat: DropDaw set initial value --- jans-cli-tui/jans-cli-tui.py | 14 +++- jans-cli-tui/models/oauth.py | 5 +- .../wui_components/edit_client_dialog.py | 27 ++++--- jans-cli-tui/wui_components/jans_drop_down.py | 70 ++++++++++--------- 4 files changed, 67 insertions(+), 49 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index bcc6b7dbc72..aa79276fc15 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -401,9 +401,17 @@ def getTitledRadioButton(self, title, name, values, current_value=None, jans_hel rl.window.jans_name = name rl.window.jans_help = jans_help rl.window.me = rl - li, rl2, width = self.handle_long_string(title,values,rl) + li, rl2, width = self.handle_long_string(title, values, rl) - return VSplit([Label(text=li, width=width,style=style), rl2],) + return VSplit([Label(text=li, width=width, style=style), rl2],) + + + def getTitledWidget(self, title, name, widget, style=''): + widget.window.me = widget + widget.window.jans_name = name + li, w2, width = self.handle_long_string(title, widget.values, widget) + + return VSplit([Label(text=li, width=width, style=style), widget]) # ----------------------------------------------------------------- # def getButton(self, text, name, jans_help, handler=None): @@ -529,4 +537,4 @@ def run(): if __name__ == "__main__": - run() \ No newline at end of file + run() diff --git a/jans-cli-tui/models/oauth.py b/jans-cli-tui/models/oauth.py index 587d5b475e0..eddbce8d56e 100644 --- a/jans-cli-tui/models/oauth.py +++ b/jans-cli-tui/models/oauth.py @@ -43,7 +43,6 @@ from wui_components.jans_dialog_with_nav import JansDialogWithNav from wui_components.edit_client_dialog import EditClientDialog from wui_components.jans_drop_down import DropDownWidget -from wui_components.jans_data_picker import DateSelectWidget class JansAuthServer: @@ -270,7 +269,7 @@ def save_client(self, dialog): return True def add_client(self): - dialog = EditClientDialog(self, title="Add Client", data={}, save_handler=self.save_client) + dialog = EditClientDialog(self, title="Add Client", data={'tokenEndpointAuthMethodsSupported':'private_key_jwt'}, save_handler=self.save_client) result = self.show_jans_dialog(dialog) def delete_client(self, selected, event): @@ -298,4 +297,4 @@ async def coroutine(): self.oauth_get_clients() return result - ensure_future(coroutine()) \ No newline at end of file + ensure_future(coroutine()) diff --git a/jans-cli-tui/wui_components/edit_client_dialog.py b/jans-cli-tui/wui_components/edit_client_dialog.py index 4a6ae5d8374..5ea34b968b4 100644 --- a/jans-cli-tui/wui_components/edit_client_dialog.py +++ b/jans-cli-tui/wui_components/edit_client_dialog.py @@ -89,7 +89,15 @@ def prepare_tabs(self): self.myparent.getTitledText("Client Name", name='displayName', value=self.data.get('displayName',''), style='green'), self.myparent.getTitledText("Client Secret", name='clientSecret', value=self.data.get('clientSecret',''), style='green'), self.myparent.getTitledText("Description", name='description', value=self.data.get('description',''), style='green'), - DropDownWidget(entries=[('client_secret_basic','client_secret_basic'),('client_secret_post','client_secret_post'),('client_secret_jwt','client_secret_jwt'),('private_key_jwt','private_key_jwt')]), + self.myparent.getTitledWidget( + "Authn Method token endpoint", + name='tokenEndpointAuthMethodsSupported', + widget=DropDownWidget( + values=[('client_secret_basic', 'client_secret_basic'), ('client_secret_post', 'client_secret_post'), ('client_secret_jwt', 'client_secret_jwt'), ('private_key_jwt', 'private_key_jwt')], + value=self.data.get('tokenEndpointAuthMethodsSupported') + ), + style='green' + ), self.myparent.getTitledRadioButton("Subject Type", name='subjectType', values=[('public', 'Public'),('pairwise', 'Pairwise')], current_value=self.data.get('subjectType'), style='green'), self.myparent.getTitledCheckBoxList("Grant", name='grantTypes', values=[('authorization_code', 'Authorization Code'), ('refresh_token', 'Refresh Token'), ('urn:ietf:params:oauth:grant-type:uma-ticket', 'UMA Ticket'), ('client_credentials', 'Client Credentials'), ('password', 'Password'), ('implicit', 'Implicit')], current_values=self.data.get('grantTypes', []), style='green'), self.myparent.getTitledCheckBoxList("Response Types", name='responseTypes', values=['code', 'token', 'id_token'], current_values=self.data.get('responseTypes', []), style='green'), @@ -162,8 +170,8 @@ def prepare_tabs(self): self.myparent.getTitledRadioButton("PRT token type", name='applicationType!', values=['JWT', 'Reference'], current_value=self.data.get('applicationType!'),style='green'), self.myparent.getTitledText(title ="Claims redirect URI", name='claimRedirectUris', value=self.data.get('claimRedirectUris',''),style='green'), - DropDownWidget(entries=[('rptClaimsScripts','rptClaimsScripts')]), - DropDownWidget(entries=[('claimRedirectUris','claimRedirectUris')]), + DropDownWidget(values=[('rptClaimsScripts','rptClaimsScripts')]), + DropDownWidget(values=[('claimRedirectUris','claimRedirectUris')]), Label(text="tabel",style='blue'), ## TODO with Jans VerticalNav @@ -237,13 +245,12 @@ def prepare_tabs(self): ) self.tabs['Client Scripts'] = HSplit([ - DropDownWidget(entries=[('spontaneousScopes','spontaneousScopes')]), - DropDownWidget(entries=[('updateTokenScriptDns','updateTokenScriptDns')]), - DropDownWidget(entries=[('postAuthnScripts','postAuthnScripts')]), - DropDownWidget(entries=[('introspectionScripts','introspectionScripts')]), - DropDownWidget(entries=[('dynamicRegistrationAllowedPasswordGrantScopes','dynamicRegistrationAllowedPasswordGrantScopes')]), - DropDownWidget(entries=[('consentGatheringScripts','consentGatheringScripts')]), - + DropDownWidget(values=[('spontaneousScopes','spontaneousScopes')]), + DropDownWidget(values=[('updateTokenScriptDns','updateTokenScriptDns')]), + DropDownWidget(values=[('postAuthnScripts','postAuthnScripts')]), + DropDownWidget(values=[('introspectionScripts','introspectionScripts')]), + DropDownWidget(values=[('dynamicRegistrationAllowedPasswordGrantScopes','dynamicRegistrationAllowedPasswordGrantScopes')]), + DropDownWidget(values=[('consentGatheringScripts','consentGatheringScripts')]), ] ) diff --git a/jans-cli-tui/wui_components/jans_drop_down.py b/jans-cli-tui/wui_components/jans_drop_down.py index 109b92b6e63..815548c8029 100644 --- a/jans-cli-tui/wui_components/jans_drop_down.py +++ b/jans-cli-tui/wui_components/jans_drop_down.py @@ -9,18 +9,25 @@ from prompt_toolkit.layout.dimension import D class JansSelectBox: - def __init__(self, entries=[],height=4,rotatable_up=True,rotatable_down=True): - self.entries = entries - self.selected_line = 0 + def __init__(self, values=[], value=None, height=4, rotatable_up=True, rotatable_down=True): + open("/tmp/aaa.txt", "a").write(str(value)+'\n') + self.values = values + self.value = value + + for i, val in enumerate(values): + if val[0] == value: + self.selected_line = i + break + else: + self.selected_line = 0 + # --------------------------------------------------- # - self.height=min(len(self.entries),height) + self.height=min(len(self.values), height) self.rotatable_up = rotatable_up self.rotatable_down = rotatable_down - self.firstValue = self.entries[0] - self.lastValue = self.entries[-1] # --------------------------------------------------- # - self.container =HSplit(children=[ Window( + self.container =HSplit(children=[ Window( content=FormattedTextControl( text=self._get_formatted_text, focusable=True, @@ -32,13 +39,13 @@ def __init__(self, entries=[],height=4,rotatable_up=True,rotatable_down=True): right_margins=[ScrollbarMargin(display_arrows=True),], wrap_lines=True, allow_scroll_beyond_bottom=True, - - )]) def _get_formatted_text(self): + open("/tmp/aaa.txt", "a").write(str(self.selected_line)+'\n') + result = [] - for i, entry in enumerate(self.entries): + for i, entry in enumerate(self.values): if i == self.selected_line: result.append(HTML(''.format('#ADD8E6', entry[1]))) else: @@ -49,47 +56,42 @@ def _get_formatted_text(self): def shift(self,seq, n): - return seq[n:]+seq[:n] + return seq[n:]+seq[:n] def up(self): - if self.selected_line == 0 : - if self.rotatable_up and self.entries[self.selected_line] == self.firstValue: + if self.selected_line == 0 : + if self.rotatable_up and self.values[self.selected_line] == self.values[0]: pass else : - self.entries = self.shift(self.entries,-1) + self.values = self.shift(self.values,-1) else : - self.selected_line = (self.selected_line - 1) % (self.height) + self.selected_line = (self.selected_line - 1) % (self.height) def down(self): - if self.selected_line +1 == (self.height) : - if self.rotatable_down and self.entries[self.selected_line] == self.lastValue: + if self.selected_line +1 == (self.height): + if self.rotatable_down and self.values[self.selected_line] == self.values[-1]: pass else: - self.entries = self.shift(self.entries,1) + self.values = self.shift(self.values, 1) else : - self.selected_line = (self.selected_line + 1) % (self.height) + self.selected_line = (self.selected_line + 1) % (self.height) - ### to jump the holl height - # if self.selected_line +1 < (self.height) : - # self.selected_line = (self.selected_line + 1) % (self.height) - # elif self.selected_line +1 == (self.height) : - # self.entries = self.shift(self.entries,self.height) - # self.selected_line = (self.selected_line + 1) % (self.height) def __pt_container__(self): return self.container class DropDownWidget: - def __init__(self, entries=[]): - self.entries = entries - if self.entries: ## should be replaced with the selected from data. - self.text = self.entries[0][1] + def __init__(self, values=[], value=None): + self.values = values + for val in values: + if val[0] == value: + self.text = val[1] + break else: - self.text = "Enter to Select" - + self.text = self.values[0][1] if self.values else "Enter to Select" self.dropdown = True self.window = Window( @@ -99,7 +101,7 @@ def __init__(self, entries=[]): key_bindings=self._get_key_bindings(), ), height=D()) #5 ## large sized enties get >> (window too small) - self.select_box = JansSelectBox(entries=self.entries,rotatable_down=True,rotatable_up=True,height=4) + self.select_box = JansSelectBox(values=self.values, value=value, rotatable_down=True, rotatable_up=True, height=4) self.select_box_float = Float(content=self.select_box, xcursor=True, ycursor=True) @@ -122,9 +124,11 @@ def _enter(event) -> None: if self.select_box_float not in get_app().layout.container.floats: get_app().layout.container.floats.append(self.select_box_float) else: - self.text = self.select_box.entries[self.select_box.selected_line][1] + self.text = self.select_box.values[self.select_box.selected_line][1] get_app().layout.container.floats.remove(self.select_box_float) + self.value = self.select_box.value + @kb.add("up") def _up(event): self.select_box.up() From 4e75f90fe7b13e0a7d2ddf53d61842d20d6e0e0d Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Mon, 22 Aug 2022 13:44:34 +0300 Subject: [PATCH 041/364] fix jans-cli relocate files --- jans-cli-tui/jans-cli-tui.py | 3 +-- jans-cli-tui/models/__init__.py | 0 .../{wui_components => models/oauth}/edit_client_dialog.py | 0 .../{wui_components => models/oauth}/edit_scope_dialog.py | 0 jans-cli-tui/models/{ => oauth}/oauth.py | 4 +++- jans-cli-tui/wui_components/jans_drop_down.py | 3 --- 6 files changed, 4 insertions(+), 6 deletions(-) create mode 100644 jans-cli-tui/models/__init__.py rename jans-cli-tui/{wui_components => models/oauth}/edit_client_dialog.py (100%) rename jans-cli-tui/{wui_components => models/oauth}/edit_scope_dialog.py (100%) rename jans-cli-tui/models/{ => oauth}/oauth.py (98%) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index aa79276fc15..0853c7050d5 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -49,7 +49,6 @@ # -------------------------------------------------------------------------- # from cli import config_cli -from wui_components.edit_scope_dialog import EditScopeDialog from wui_components.jans_cli_dialog import JansGDialog from wui_components.jans_nav_bar import JansNavBar from wui_components.jans_side_nav_bar import JansSideNavBar @@ -59,7 +58,7 @@ from cli_style import style -from models.oauth import JansAuthServer +from models.oauth.oauth import JansAuthServer from pathlib import Path # -------------------------------------------------------------------------- # diff --git a/jans-cli-tui/models/__init__.py b/jans-cli-tui/models/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/jans-cli-tui/wui_components/edit_client_dialog.py b/jans-cli-tui/models/oauth/edit_client_dialog.py similarity index 100% rename from jans-cli-tui/wui_components/edit_client_dialog.py rename to jans-cli-tui/models/oauth/edit_client_dialog.py diff --git a/jans-cli-tui/wui_components/edit_scope_dialog.py b/jans-cli-tui/models/oauth/edit_scope_dialog.py similarity index 100% rename from jans-cli-tui/wui_components/edit_scope_dialog.py rename to jans-cli-tui/models/oauth/edit_scope_dialog.py diff --git a/jans-cli-tui/models/oauth.py b/jans-cli-tui/models/oauth/oauth.py similarity index 98% rename from jans-cli-tui/models/oauth.py rename to jans-cli-tui/models/oauth/oauth.py index eddbce8d56e..23b01df87fe 100644 --- a/jans-cli-tui/models/oauth.py +++ b/jans-cli-tui/models/oauth/oauth.py @@ -41,8 +41,10 @@ from wui_components.jans_vetrical_nav import JansVerticalNav from wui_components.jans_dialog import JansDialog from wui_components.jans_dialog_with_nav import JansDialogWithNav -from wui_components.edit_client_dialog import EditClientDialog from wui_components.jans_drop_down import DropDownWidget +from models.oauth.edit_client_dialog import EditClientDialog +from models.oauth.edit_scope_dialog import EditScopeDialog + class JansAuthServer: diff --git a/jans-cli-tui/wui_components/jans_drop_down.py b/jans-cli-tui/wui_components/jans_drop_down.py index 815548c8029..08b5de79cde 100644 --- a/jans-cli-tui/wui_components/jans_drop_down.py +++ b/jans-cli-tui/wui_components/jans_drop_down.py @@ -10,7 +10,6 @@ class JansSelectBox: def __init__(self, values=[], value=None, height=4, rotatable_up=True, rotatable_down=True): - open("/tmp/aaa.txt", "a").write(str(value)+'\n') self.values = values self.value = value @@ -42,8 +41,6 @@ def __init__(self, values=[], value=None, height=4, rotatable_up=True, rotatable )]) def _get_formatted_text(self): - open("/tmp/aaa.txt", "a").write(str(self.selected_line)+'\n') - result = [] for i, entry in enumerate(self.values): if i == self.selected_line: From a1bd2a85a32fcf56c71cd2321cedabc4f533b07a Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Mon, 22 Aug 2022 14:28:29 +0300 Subject: [PATCH 042/364] feat: jans-cli enable saving value of dropdown widget --- .../models/oauth/edit_client_dialog.py | 51 ++++++++++++++----- jans-cli-tui/models/oauth/oauth.py | 8 ++- jans-cli-tui/wui_components/jans_drop_down.py | 30 ++++++++--- 3 files changed, 66 insertions(+), 23 deletions(-) diff --git a/jans-cli-tui/models/oauth/edit_client_dialog.py b/jans-cli-tui/models/oauth/edit_client_dialog.py index 5ea34b968b4..27a57d6174d 100644 --- a/jans-cli-tui/models/oauth/edit_client_dialog.py +++ b/jans-cli-tui/models/oauth/edit_client_dialog.py @@ -111,11 +111,11 @@ def prepare_tabs(self): ) self.tabs['Tokens'] = HSplit([ - self.myparent.getTitledRadioButton("Access Token Type", name='accessTokenAsJwt', values=['JWT','Reference'], current_value=self.data.get('accessTokenAsJwt'),style='green'), - self.myparent.getTitledCheckBox("Incliude Claims in id_token", name='includeClaimsInIdToken', checked=self.data.get('includeClaimsInIdToken'),style='green'), - self.myparent.getTitledCheckBox("Run introspection script before JWT access token creation", name='runIntrospectionScriptBeforeAccessTokenAsJwtCreationAndIncludeClaims', checked=self.data.get('runIntrospectionScriptBeforeAccessTokenAsJwtCreationAndIncludeClaims'),style='green'), - self.myparent.getTitledText(title="Token binding confirmation method for id_token", name='idTokenTokenBindingCnf',value=self.data.get('idTokenTokenBindingCnf',''),style='green'), - self.myparent.getTitledText(title="Access token additional audiences", name='additionalAudience',value=self.data.get('additionalAudience',''),style='green'), + self.myparent.getTitledRadioButton("Access Token Type", name='accessTokenAsJwt', values=['JWT','Reference'], current_value=self.data.get('accessTokenAsJwt'), style='green'), + self.myparent.getTitledCheckBox("Incliude Claims in id_token", name='includeClaimsInIdToken', checked=self.data.get('includeClaimsInIdToken'), style='green'), + self.myparent.getTitledCheckBox("Run introspection script before JWT access token creation", name='runIntrospectionScriptBeforeAccessTokenAsJwtCreationAndIncludeClaims', checked=self.data.get('runIntrospectionScriptBeforeAccessTokenAsJwtCreationAndIncludeClaims'), style='green'), + self.myparent.getTitledText(title="Token binding confirmation method for id_token", name='idTokenTokenBindingCnf',value=self.data.get('idTokenTokenBindingCnf',''), style='green'), + self.myparent.getTitledText(title="Access token additional audiences", name='additionalAudience',value=self.data.get('additionalAudience',''), style='green'), VSplit([ Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3) ]), @@ -170,9 +170,18 @@ def prepare_tabs(self): self.myparent.getTitledRadioButton("PRT token type", name='applicationType!', values=['JWT', 'Reference'], current_value=self.data.get('applicationType!'),style='green'), self.myparent.getTitledText(title ="Claims redirect URI", name='claimRedirectUris', value=self.data.get('claimRedirectUris',''),style='green'), - DropDownWidget(values=[('rptClaimsScripts','rptClaimsScripts')]), - DropDownWidget(values=[('claimRedirectUris','claimRedirectUris')]), - + #self.myparent.getTitledWidget( + # "RPT Mofification Script", + # name='', + # widget=DropDownWidget(values=[('rptClaimsScripts','rptClaimsScripts')]), + # value='', + # ), + #self.myparent.getTitledWidget( + # title="title here", + # name='', + # widget=DropDownWidget(values=[('claimRedirectUris','claimRedirectUris')]), + # value='', + # ), Label(text="tabel",style='blue'), ## TODO with Jans VerticalNav ] @@ -245,12 +254,26 @@ def prepare_tabs(self): ) self.tabs['Client Scripts'] = HSplit([ - DropDownWidget(values=[('spontaneousScopes','spontaneousScopes')]), - DropDownWidget(values=[('updateTokenScriptDns','updateTokenScriptDns')]), - DropDownWidget(values=[('postAuthnScripts','postAuthnScripts')]), - DropDownWidget(values=[('introspectionScripts','introspectionScripts')]), - DropDownWidget(values=[('dynamicRegistrationAllowedPasswordGrantScopes','dynamicRegistrationAllowedPasswordGrantScopes')]), - DropDownWidget(values=[('consentGatheringScripts','consentGatheringScripts')]), + + #self.myparent.getTitledWidget( + # widget=DropDownWidget(values=[('spontaneousScopes','spontaneousScopes')]), + #), + #self.myparent.getTitledWidget( + # widget=DropDownWidget(values=[('updateTokenScriptDns','updateTokenScriptDns')]), + #), + #self.myparent.getTitledWidget( + # widget=DropDownWidget(values=[('postAuthnScripts','postAuthnScripts')]), + #), + #self.myparent.getTitledWidget( + # widget=DropDownWidget(values=[('introspectionScripts','introspectionScripts')]), + #), + #self.myparent.getTitledWidget( + # widget=DropDownWidget(values=[('dynamicRegistrationAllowedPasswordGrantScopes','dynamicRegistrationAllowedPasswordGrantScopes')]), + #), + #self.myparent.getTitledWidget( + # widget=DropDownWidget(values=[('consentGatheringScripts','consentGatheringScripts')]), + #), + ] ) diff --git a/jans-cli-tui/models/oauth/oauth.py b/jans-cli-tui/models/oauth/oauth.py index 23b01df87fe..91a53bd0072 100644 --- a/jans-cli-tui/models/oauth/oauth.py +++ b/jans-cli-tui/models/oauth/oauth.py @@ -242,8 +242,9 @@ def save_client(self, dialog): data = {} for tab in dialog.tabs: for item in dialog.tabs[tab].children: - if hasattr(item, 'children') and hasattr(item.children[1], 'jans_name'): + if hasattr(item, 'children') and len(item.children)>1 and hasattr(item.children[1], 'jans_name'): key_ = item.children[1].jans_name + self.logger.debug(key_ + ':' + str(type(item.children[1].me))) if isinstance(item.children[1].me, prompt_toolkit.widgets.base.TextArea): value_ = item.children[1].me.text elif isinstance(item.children[1].me, prompt_toolkit.widgets.base.CheckboxList): @@ -252,6 +253,9 @@ def save_client(self, dialog): value_ = item.children[1].me.current_value elif isinstance(item.children[1].me, prompt_toolkit.widgets.base.Checkbox): value_ = item.children[1].me.checked + elif isinstance(item.children[1].me, DropDownWidget): + value_ = item.children[1].me.value + data[key_] = value_ for list_key in ('redirectUris', 'scopes'): @@ -259,6 +263,8 @@ def save_client(self, dialog): data[list_key] = data[list_key].splitlines() self.logger.debug(str(data)) + + response = self.cli_object.process_command_by_id( operation_id='post-oauth-openid-clients', url_suffix='', diff --git a/jans-cli-tui/wui_components/jans_drop_down.py b/jans-cli-tui/wui_components/jans_drop_down.py index 08b5de79cde..538e67255b5 100644 --- a/jans-cli-tui/wui_components/jans_drop_down.py +++ b/jans-cli-tui/wui_components/jans_drop_down.py @@ -11,14 +11,7 @@ class JansSelectBox: def __init__(self, values=[], value=None, height=4, rotatable_up=True, rotatable_down=True): self.values = values - self.value = value - - for i, val in enumerate(values): - if val[0] == value: - self.selected_line = i - break - else: - self.selected_line = 0 + self.set_value(value) # --------------------------------------------------- # self.height=min(len(self.values), height) @@ -40,6 +33,18 @@ def __init__(self, values=[], value=None, height=4, rotatable_up=True, rotatable allow_scroll_beyond_bottom=True, )]) + def set_value(self, value): + self.value = value + + for i, val in enumerate(self.values): + if val[0] == value: + self.selected_line = i + break + else: + self.selected_line = 0 + + + def _get_formatted_text(self): result = [] for i, entry in enumerate(self.values): @@ -101,6 +106,15 @@ def __init__(self, values=[], value=None): self.select_box = JansSelectBox(values=self.values, value=value, rotatable_down=True, rotatable_up=True, height=4) self.select_box_float = Float(content=self.select_box, xcursor=True, ycursor=True) + @property + def value(self): + return self.select_box.value + + + @value.setter + def value(self, value): + self.select_box.set_value(value) + def _get_text(self): if get_app().layout.current_window is self.window: From 9e0a34d57b46e852b51be5d8a3bf5b1960955f3e Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Mon, 22 Aug 2022 15:36:11 +0300 Subject: [PATCH 043/364] fix: jans-cli check expired token --- jans-cli-tui/jans-cli-tui.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index 0853c7050d5..469c163de6b 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -202,9 +202,10 @@ def create_cli(self): self.press_tab() - if status is not True: + if status not in (True, 'ID Token is expired'): buttons = [Button("OK", handler=self.jans_creds_dialog)] self.show_message("Error getting Connection Config Api", status, buttons=buttons) + else: if not test_client and not self.cli_object.access_token: From de38bee5d208fc2baaae472ba21c98cc9059830e Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Mon, 22 Aug 2022 15:54:43 +0300 Subject: [PATCH 044/364] fix: jans-cli status bar as FormattedTextControl --- jans-cli-tui/jans-cli-tui.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index 469c163de6b..1bafe6b3ec5 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -112,7 +112,9 @@ def __init__(self): self.yes_button = Button(text="Yes", handler=accept_yes) self.no_button = Button(text="No", handler=accept_no) - self.status_bar = TextArea(style="class:status", height=1, focusable=False) + self.status_bar = Window( + FormattedTextControl(self.update_status_bar), style="class:status", height=1 + ) self.prapare_dialogs() @@ -208,7 +210,7 @@ def create_cli(self): else: if not test_client and not self.cli_object.access_token: - + response = self.cli_object.get_device_verification_code() result = response.json() @@ -282,11 +284,9 @@ def prapare_dialogs(self): def focus_next(self, ev): focus_next(ev) - self.update_status_bar() def focus_previous(self, ev): focus_previous(ev) - self.update_status_bar() def set_keybindings(self): # Global key bindings. @@ -436,7 +436,7 @@ def update_status_bar(self, text=None): wname = getattr(self.layout.current_window, 'jans_name', 'NA') text = help_text_dict.get(wname, '') - self.status_bar.text = text + return text def main_nav_selection_changed(self, selection): if hasattr(self, selection+'_set_center_frame'): From cef97e7f9cc898b94083ecb8cdffb813e6863d30 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Mon, 22 Aug 2022 06:42:41 -0700 Subject: [PATCH 045/364] before merge --- jans-cli-tui/models/oauth.py | 1 + .../wui_components/jans_data_picker.py | 186 ++++++++++++++++++ 2 files changed, 187 insertions(+) create mode 100644 jans-cli-tui/wui_components/jans_data_picker.py diff --git a/jans-cli-tui/models/oauth.py b/jans-cli-tui/models/oauth.py index 587d5b475e0..149ccaebf8e 100644 --- a/jans-cli-tui/models/oauth.py +++ b/jans-cli-tui/models/oauth.py @@ -80,6 +80,7 @@ def oauth_prepare_containers(self): self.getButton(text="Get Clients", name='oauth:clients:get', jans_help="Retreive first 10 OpenID Connect clients", handler=self.oauth_get_clients), self.getTitledText('Search', name='oauth:clients:search', jans_help='Press enter to perform search'), self.getButton(text="Add Client", name='oauth:clients:add', jans_help="To add a new client press this button", handler=self.add_client), + DateSelectWidget(), ], padding=3, width=D(), diff --git a/jans-cli-tui/wui_components/jans_data_picker.py b/jans-cli-tui/wui_components/jans_data_picker.py new file mode 100644 index 00000000000..653524fc41a --- /dev/null +++ b/jans-cli-tui/wui_components/jans_data_picker.py @@ -0,0 +1,186 @@ + +from prompt_toolkit.application.current import get_app +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.layout.containers import Float, HSplit, Window +from prompt_toolkit.layout.controls import FormattedTextControl +from prompt_toolkit.formatted_text import HTML, merge_formatted_text +from prompt_toolkit.layout.margins import ScrollbarMargin +from prompt_toolkit.key_binding.bindings.focus import focus_next +from prompt_toolkit.layout.dimension import D +from prompt_toolkit.layout.containers import Float, FloatContainer, HSplit, Window, VSplit +from prompt_toolkit.widgets import Button, Label, TextArea +from prompt_toolkit.layout.containers import ( + ConditionalContainer, + Float, + HSplit, + VSplit, + VerticalAlign, + HorizontalAlign, + DynamicContainer, + FloatContainer, + Window +) +import calendar + +#### not finished yet + +class JansSelectDate: + def __init__(self, entries=[]): + self.entries = entries + self.selected_line = 0 + self.months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] + self.current_month = 0 + self.current_year = 2022 + + + self.month_text = Label(text=self.months[self.current_month],width=8) + + self.container =HSplit(children=[ + VSplit([ + Button(text='<',left_symbol='',right_symbol='',width=1), + Label(text=self.months[self.current_month],width=len(self.months[self.current_month])), + Button(text='>',left_symbol='',right_symbol='',width=1), + Window(width=2,char=' '), + Button(text='<',left_symbol='',right_symbol='',width=1), + Label(text=str(self.current_year),width=len(str(self.current_year))), + Button(text='>',left_symbol='',right_symbol='',width=1 ) + ],style="bg:#4D4D4D",padding=1), + + + + Window( + content=FormattedTextControl( + text=self._get_formatted_text, + focusable=True, + + ), + height=5, + cursorline=False, + # width=D(), #15, + style="bg:#4D4D4D", + right_margins=[ScrollbarMargin(display_arrows=True),], + wrap_lines=True + ), + + + + ]) + + + def _get_formatted_text(self): + + calendar.monthcalendar(2022, self.months.index('February')+1) + result = [] + cur_month = self.months.index('February')+1 + + # for cur_month in range(len(months)): + # if i == calendar.monthcalendar(2022, months.index(current_month)+1): + month_week_list = calendar.monthcalendar(2022, self.months.index(self.months[cur_month])+1) + + for k in range(len(month_week_list)) : + dum_list =[] + # not_selectrion =month_week_list[k][-1].count(0) + for week in month_week_list[k]: + if len(str(week)) ==1 : + dum_list.append(' '+str(week)) + else : + dum_list.append(str(week)) + month_week_list[k] = dum_list + result.append(HTML(''.format('#ADD8E6',' '.join(month_week_list[k])))) + # else: + # result.append(HTML('{}'.format(entry[1]))) + result.append("\n") + + + + return merge_formatted_text(result) + + def inc_month(self): + self.current_month+=1 + + + def dec_month(self): + self.current_month-=1 + + def inc_year(self): + self.current_year+=1 + + + def dec_year(self): + self.current_year-=1 + + + def up(self): + self.selected_line = (self.selected_line - 1) % len(self.entries) + + + def down(self): + self.selected_line = (self.selected_line + 1) % len(self.entries) + + def __pt_container__(self): + return self.container + + +class DateSelectWidget: + def __init__(self): + self.entries = [('Date','Date')] + if self.entries: ## should be replaced with the selected from data. + self.text = self.entries[0][1] + else: + self.text = "Enter to Select" + + + self.dropdown = True + self.window = Window( + content=FormattedTextControl( + text=self._get_text, + focusable=True, + key_bindings=self._get_key_bindings(), + ), height= 10) #D()) #5 ## large sized enties get >> (window too small) + + self.select_box = JansSelectDate(self.entries) + self.select_box_float = Float(content=self.select_box, xcursor=True, ycursor=True) + + + def _get_text(self): + if get_app().layout.current_window is self.window: + return HTML('> <'.format('#00FF00', self.text)) + return '> {} <'.format(self.text) + + + def _get_key_bindings(self): + kb = KeyBindings() + + + def _focus_next(event): + focus_next(event) + + @kb.add("enter") + def _enter(event) -> None: + + if self.select_box_float not in get_app().layout.container.floats: + get_app().layout.container.floats.insert(0, self.select_box_float) + else: + self.text = self.select_box.entries[self.select_box.selected_line][1] + get_app().layout.container.floats.remove(self.select_box_float) + + @kb.add("up") + def _up(event): + self.select_box.up() + + @kb.add("down") + def _up(event): + self.select_box.down() + + @kb.add("escape") + @kb.add("tab") + def _(event): + if self.select_box_float in get_app().layout.container.floats: + get_app().layout.container.floats.remove(self.select_box_float) + + _focus_next(event) + + return kb + + def __pt_container__(self): + return self.window From 133f244a5e18e36d02c512df9a119e651f6560d5 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Mon, 22 Aug 2022 07:52:39 -0700 Subject: [PATCH 046/364] feat:jans-cli add datepicker widged --- .../models/oauth/edit_client_dialog.py | 65 +++--- jans-cli-tui/models/oauth/oauth.py | 3 +- .../wui_components/jans_data_picker.py | 198 +++++++++++++----- 3 files changed, 181 insertions(+), 85 deletions(-) diff --git a/jans-cli-tui/models/oauth/edit_client_dialog.py b/jans-cli-tui/models/oauth/edit_client_dialog.py index 27a57d6174d..572c70be644 100644 --- a/jans-cli-tui/models/oauth/edit_client_dialog.py +++ b/jans-cli-tui/models/oauth/edit_client_dialog.py @@ -43,6 +43,7 @@ from wui_components.jans_side_nav_bar import JansSideNavBar from wui_components.jans_cli_dialog import JansGDialog from wui_components.jans_drop_down import DropDownWidget +from wui_components.jans_data_picker import DateSelectWidget class EditClientDialog(JansGDialog): def __init__(self, parent, title, data, buttons=[], save_handler=None): @@ -169,19 +170,19 @@ def prepare_tabs(self): Label("UMA",style='bold'), self.myparent.getTitledRadioButton("PRT token type", name='applicationType!', values=['JWT', 'Reference'], current_value=self.data.get('applicationType!'),style='green'), self.myparent.getTitledText(title ="Claims redirect URI", name='claimRedirectUris', value=self.data.get('claimRedirectUris',''),style='green'), - - #self.myparent.getTitledWidget( - # "RPT Mofification Script", - # name='', - # widget=DropDownWidget(values=[('rptClaimsScripts','rptClaimsScripts')]), - # value='', - # ), - #self.myparent.getTitledWidget( - # title="title here", - # name='', - # widget=DropDownWidget(values=[('claimRedirectUris','claimRedirectUris')]), - # value='', - # ), + + self.myparent.getTitledWidget( + "RPT Mofification Script", + name='rptClaimsScripts', + widget=DropDownWidget( + values=[('rptClaimsScripts','rptClaimsScripts')]), + ), + self.myparent.getTitledWidget( + title="title here", + name='claimRedirectUris', + widget=DropDownWidget( + values=[('claimRedirectUris','claimRedirectUris')]), + ), Label(text="tabel",style='blue'), ## TODO with Jans VerticalNav ] @@ -235,8 +236,13 @@ def prepare_tabs(self): self.myparent.getTitledText("Request URIs", name='requestUris', value=self.data.get('requestUris',''),style='green'), Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3,) ]) , + self.myparent.getTitledWidget( + title="Defult ACR", + name='authorizedAcrValues', + widget=DropDownWidget( + values=[('authorizedAcrValues','authorizedAcrValues')]), + ), - Label(text="Dropdown 3",style='blue'), VSplit([ # self.myparent.getTitledText("Allowed ACRs", name='clientSecret', value=self.data.get('clientSecret',''),style='green'), @@ -247,7 +253,12 @@ def prepare_tabs(self): VSplit([ # self.myparent.getTitledCheckBox("Client Experiation Date", name='id_token_claims', checked=self.data.get('id_token_claims'),style='green'), # attached to date - Label(text="Pick Date",style='blue'), + VSplit([Label(text="Client Experiation Date", + width=len("Client Experiation Date"),), + DateSelectWidget(data="20/2/1997")]), + + + ]) , ],width=D() @@ -255,24 +266,24 @@ def prepare_tabs(self): self.tabs['Client Scripts'] = HSplit([ - #self.myparent.getTitledWidget( + # self.myparent.getTitledWidget( # widget=DropDownWidget(values=[('spontaneousScopes','spontaneousScopes')]), - #), - #self.myparent.getTitledWidget( + # ), + # self.myparent.getTitledWidget( # widget=DropDownWidget(values=[('updateTokenScriptDns','updateTokenScriptDns')]), - #), - #self.myparent.getTitledWidget( + # ), + # self.myparent.getTitledWidget( # widget=DropDownWidget(values=[('postAuthnScripts','postAuthnScripts')]), - #), - #self.myparent.getTitledWidget( + # ), + # self.myparent.getTitledWidget( # widget=DropDownWidget(values=[('introspectionScripts','introspectionScripts')]), - #), - #self.myparent.getTitledWidget( + # ), + # self.myparent.getTitledWidget( # widget=DropDownWidget(values=[('dynamicRegistrationAllowedPasswordGrantScopes','dynamicRegistrationAllowedPasswordGrantScopes')]), - #), - #self.myparent.getTitledWidget( + # ), + # self.myparent.getTitledWidget( # widget=DropDownWidget(values=[('consentGatheringScripts','consentGatheringScripts')]), - #), + # ), ] ) diff --git a/jans-cli-tui/models/oauth/oauth.py b/jans-cli-tui/models/oauth/oauth.py index cb21532bfad..4f16aeb9d30 100644 --- a/jans-cli-tui/models/oauth/oauth.py +++ b/jans-cli-tui/models/oauth/oauth.py @@ -44,6 +44,7 @@ from wui_components.jans_drop_down import DropDownWidget from models.oauth.edit_client_dialog import EditClientDialog from models.oauth.edit_scope_dialog import EditScopeDialog +from wui_components.jans_data_picker import DateSelectWidget class JansAuthServer: @@ -81,7 +82,7 @@ def oauth_prepare_containers(self): self.getButton(text="Get Clients", name='oauth:clients:get', jans_help="Retreive first 10 OpenID Connect clients", handler=self.oauth_get_clients), self.getTitledText('Search', name='oauth:clients:search', jans_help='Press enter to perform search'), self.getButton(text="Add Client", name='oauth:clients:add', jans_help="To add a new client press this button", handler=self.add_client), - DateSelectWidget(), + ], padding=3, width=D(), diff --git a/jans-cli-tui/wui_components/jans_data_picker.py b/jans-cli-tui/wui_components/jans_data_picker.py index 653524fc41a..7e756bbe2b5 100644 --- a/jans-cli-tui/wui_components/jans_data_picker.py +++ b/jans-cli-tui/wui_components/jans_data_picker.py @@ -20,39 +20,41 @@ FloatContainer, Window ) + import calendar #### not finished yet class JansSelectDate: - def __init__(self, entries=[]): - self.entries = entries - self.selected_line = 0 + + def __init__(self, date=''): + self.date = date self.months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] - self.current_month = 0 - self.current_year = 2022 - - self.month_text = Label(text=self.months[self.current_month],width=8) + self.cord_y = 0 + self.cord_x = 0 + self.extract_date(self.date) + #self.depug=Label(text="Cord_y = "+str(self.cord_y) +": current_month = "+ str(self.current_month),) + self.month_label = Label(text=self.months[self.current_month-1],width=len(self.months[self.current_month-1])) + self.year_label = Label(text=str(self.current_year),width=len(str(self.current_year))) + self.container =HSplit(children=[ VSplit([ Button(text='<',left_symbol='',right_symbol='',width=1), - Label(text=self.months[self.current_month],width=len(self.months[self.current_month])), + DynamicContainer(lambda: self.month_label), Button(text='>',left_symbol='',right_symbol='',width=1), Window(width=2,char=' '), Button(text='<',left_symbol='',right_symbol='',width=1), - Label(text=str(self.current_year),width=len(str(self.current_year))), + DynamicContainer(lambda: self.year_label), Button(text='>',left_symbol='',right_symbol='',width=1 ) ],style="bg:#4D4D4D",padding=1), - - - + #DynamicContainer(lambda: self.depug), + Window( content=FormattedTextControl( text=self._get_formatted_text, focusable=True, - ), height=5, cursorline=False, @@ -60,74 +62,128 @@ def __init__(self, entries=[]): style="bg:#4D4D4D", right_margins=[ScrollbarMargin(display_arrows=True),], wrap_lines=True - ), - - - + ), ]) + def extract_date(self,date): + ### this function is for the init date >> passed date from data + day = int(date.split('/')[0]) + self.current_month = int(date.split('/')[1]) + self.current_year = int(date.split('/')[-1]) + + month_week_list = calendar.monthcalendar(int(self.current_year),int(self.current_month)) + + self.entries = month_week_list + + dum_week = [] + for week in self.entries: + dum_week = week + try : + day_index = week.index(day) + break + except: + day_index = 0 + week_index = self.entries.index(dum_week) + self.cord_y = week_index + self.cord_x = day_index + + def adjust_sizes(self,day): + if str(day) != '0': + if len(str(day)) <=1: + return ' '+str(day) + else : + return ' '+str(day) + else: + return ' ' def _get_formatted_text(self): - - calendar.monthcalendar(2022, self.months.index('February')+1) result = [] - cur_month = self.months.index('February')+1 - - # for cur_month in range(len(months)): - # if i == calendar.monthcalendar(2022, months.index(current_month)+1): - month_week_list = calendar.monthcalendar(2022, self.months.index(self.months[cur_month])+1) - - for k in range(len(month_week_list)) : - dum_list =[] - # not_selectrion =month_week_list[k][-1].count(0) - for week in month_week_list[k]: - if len(str(week)) ==1 : - dum_list.append(' '+str(week)) - else : - dum_list.append(str(week)) - month_week_list[k] = dum_list - result.append(HTML(''.format('#ADD8E6',' '.join(month_week_list[k])))) - # else: - # result.append(HTML('{}'.format(entry[1]))) - result.append("\n") - + week_line = [] + for i, week in enumerate(self.entries): + for day in range(len(week)) : + if i == self.cord_y: + if day == self.cord_x: + week_line.append(HTML(''.format(self.adjust_sizes(week[day])))) + + # result.append(HTML(''.format( day))) + else : + week_line.append(HTML('{}'.format(self.adjust_sizes(week[day])))) + else: + week_line.append(HTML('{}'.format(self.adjust_sizes(week[day])))) + + result= (week_line) + result.append("\n") return merge_formatted_text(result) def inc_month(self): - self.current_month+=1 - + if self.current_month != 12: + self.current_month+=1 + self.month_label = Label(text=self.months[self.current_month-1],width=len(self.months[self.current_month-1])) + current_date = '20/'+str(self.current_month) + '/'+str(self.current_year) + self.extract_date(current_date) def dec_month(self): - self.current_month-=1 + if self.current_month > 1: + self.current_month-=1 + self.month_label = Label(text=self.months[self.current_month-1],width=len(self.months[self.current_month-1])) + current_date = '20/'+str(self.current_month) + '/'+str(self.current_year) + self.extract_date(current_date) def inc_year(self): - self.current_year+=1 + self.current_year+=1 + self.year_label = Label(text=str(self.current_year),width=len(str(self.current_year))) + current_date = '20/'+str(self.current_month) + '/'+str(self.current_year) + self.extract_date(current_date)# 20/2/1997 def dec_year(self): + self.current_year-=1 + self.year_label = Label(text=str(self.current_year),width=len(str(self.current_year))) + current_date = '20/'+str(self.current_month) + '/'+str(self.current_year) + self.extract_date(current_date)# 20/2/1997 - - def up(self): - self.selected_line = (self.selected_line - 1) % len(self.entries) - + def up(self): + if self.cord_y == 0: + self.dec_month() + else: + self.cord_y = (self.cord_y - 1)# % 5 + #self.depug=Label(text="Cord_y = "+str(self.cord_y) +": current_month = "+ str(self.current_month),) def down(self): - self.selected_line = (self.selected_line + 1) % len(self.entries) + + if self.cord_y == 4: + self.inc_month() + else: + self.cord_y = (self.cord_y + 1)# % 5 + #self.depug=Label(text="Cord_y = "+str(self.cord_y) +": current_month = "+ str(self.current_month),) + + def right(self): + if self.cord_x == 6: + self.inc_year() + else : + self.cord_x = (self.cord_x + 1) #% 7 + #self.depug=Label(text="Cord_y = "+str(self.cord_y) +": current_month = "+ str(self.current_month) ,) + + def left(self): + if self.cord_x == 0: + self.dec_year() + else: + self.cord_x = (self.cord_x - 1)# % 7 + #self.depug=Label(text="Cord_y = "+str(self.cord_y) +": current_month = "+ str(self.current_month),) + def __pt_container__(self): return self.container class DateSelectWidget: - def __init__(self): - self.entries = [('Date','Date')] - if self.entries: ## should be replaced with the selected from data. - self.text = self.entries[0][1] - else: - self.text = "Enter to Select" + def __init__(self,data): + self.date = data #"20/2/1997" + + self.text = "Enter to Select" self.dropdown = True @@ -138,7 +194,7 @@ def __init__(self): key_bindings=self._get_key_bindings(), ), height= 10) #D()) #5 ## large sized enties get >> (window too small) - self.select_box = JansSelectDate(self.entries) + self.select_box = JansSelectDate(self.date) self.select_box_float = Float(content=self.select_box, xcursor=True, ycursor=True) @@ -159,9 +215,10 @@ def _focus_next(event): def _enter(event) -> None: if self.select_box_float not in get_app().layout.container.floats: - get_app().layout.container.floats.insert(0, self.select_box_float) + get_app().layout.container.floats.append( self.select_box_float) else: - self.text = self.select_box.entries[self.select_box.selected_line][1] + self.text = str(self.select_box.entries[self.select_box.cord_y][self.select_box.cord_x] )+'/'+ str(self.select_box.months[self.select_box.current_month-1] ) +'/'+str(self.select_box.current_year) + get_app().layout.container.floats.remove(self.select_box_float) @kb.add("up") @@ -172,6 +229,33 @@ def _up(event): def _up(event): self.select_box.down() + @kb.add("right") + def _up(event): + self.select_box.right() + + @kb.add("left") + def _up(event): + self.select_box.left() + + + + @kb.add("w") + def _up(event): + self.select_box.inc_year() + + @kb.add("s") + def _up(event): + self.select_box.dec_year() + + @kb.add("a") + def _up(event): + self.select_box.inc_month() + + @kb.add("d") + def _up(event): + self.select_box.dec_month() + + @kb.add("escape") @kb.add("tab") def _(event): From a181fe5dfd2500787d512406a35d331d6754f5fb Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Mon, 22 Aug 2022 21:05:08 +0300 Subject: [PATCH 047/364] feat: jans-cli getting help from schema --- jans-cli-tui/jans-cli-tui.py | 46 +++++++--------- .../models/oauth/edit_client_dialog.py | 52 +++++++++++++------ .../wui_components/jans_dialog_with_nav.py | 17 +++--- jans-cli-tui/wui_components/jans_nav_bar.py | 10 ---- .../wui_components/jans_side_nav_bar.py | 11 +--- 5 files changed, 64 insertions(+), 72 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index 1bafe6b3ec5..938b170fc55 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -63,19 +63,6 @@ # -------------------------------------------------------------------------- # - -help_text_dict = { - 'displayName': ("Name of the user suitable for display to end-users"), - 'clientSecret': ("The client secret. The client MAY omit the parameter if the client secret is an empty string"), - 'redirectUris': ("Redirection URI values used by the Client. One of these registered Redirection URI values must exactly match the redirect_uri parameter value used in each Authorization Request"), - 'responseTypes': ("A list of the OAuth 2.0 response_type values that the Client is declaring that it will restrict itself to using. If omitted, the default is that the Client will use only the code Response Type. Allowed values are code, token, id_token"), - 'applicationType': ("Kind of the application. The default, if omitted, is web. The defined values are native or web. Web Clients using the OAuth Implicit Grant Type must only register URLs using the HTTPS scheme as redirect_uris, they must not use localhost as the hostname. Native Clients must only register redirect_uris using custom URI schemes or URLs using the http scheme with localhost as the hostname"), - 'helper': ("To guide you through the fields"), -} - - - - home_dir = Path.home() config_dir = home_dir.joinpath('.config') config_dir.mkdir(parents=True, exist_ok=True) @@ -97,12 +84,14 @@ class JansCliApp(Application, JansAuthServer): def __init__(self): self.init_logger() self.app_started = False + self.status_bar_text = '' self.width, self.height = get_terminal_size() self.app = get_app() self.show_dialog = False ## ## ## ## self.set_keybindings() self.containers = {} # -------------------------------------------------------------------------------- # + self.dialogs = {} self.tabs = {} self.active_dialog_select = '' @@ -174,6 +163,7 @@ def __init__(self): self.check_jans_cli_ini() # ----------------------------------------------------------------------------- # + def init_logger(self): self.logger = logging.getLogger('JansCli') self.logger.setLevel(logging.DEBUG) @@ -300,7 +290,12 @@ def set_keybindings(self): def help(self,ev): self.show_message("Help",''' Edit current selection\n Display current item in JSON format\n Delete current selection''') # ----------------------------------------------------------------- # - + + def get_help_from_schema(self, schema, jans_name): + for prop in schema.get('properties', {}): + if prop == jans_name: + return schema['properties'][jans_name].get('description', '') + def handle_long_string (self,text,values,cb): lines = [] if len(text) > 20 : @@ -339,12 +334,12 @@ def handle_long_string (self,text,values,cb): Label(text=('\n')*(lines_under_value-1)) ]) else : - cd = cb + cd = cb if title_lines <= len(values) : ### if num of values (value lines) < = title_lines lines_under_title = abs(len(values) - title_lines) else : - lines_under_title = 0 + lines_under_title = 0 # first one >> solved >> title if lines_under_title >=1 : @@ -354,7 +349,7 @@ def handle_long_string (self,text,values,cb): pass return new_title , cd , width - + def getTitledText(self, title, name, value='', height=1, jans_help='', width=None,style=''): title += ': ' multiline = height > 1 @@ -406,9 +401,10 @@ def getTitledRadioButton(self, title, name, values, current_value=None, jans_hel return VSplit([Label(text=li, width=width, style=style), rl2],) - def getTitledWidget(self, title, name, widget, style=''): + def getTitledWidget(self, title, name, widget, jans_help='', style=''): widget.window.me = widget widget.window.jans_name = name + widget.window.jans_help = jans_help li, w2, width = self.handle_long_string(title, widget.values, widget) return VSplit([Label(text=li, width=width, style=style), widget]) @@ -422,19 +418,15 @@ def getButton(self, text, name, jans_help, handler=None): b.handler = handler return b - def get_statusbar_text(self): - wname = getattr(self.layout.current_window, 'jans_name', 'NA') - return help_text_dict.get(wname, '') - def update_status_bar(self, text=None): - if text: - self.status_bar.text = text + def update_status_bar(self): + text = '' + if self.status_bar_text: + text = self.status_bar_text + self.status_bar_text = '' else: if hasattr(self.layout.current_window, 'jans_help') and self.layout.current_window.jans_help: text = self.layout.current_window.jans_help - else: - wname = getattr(self.layout.current_window, 'jans_name', 'NA') - text = help_text_dict.get(wname, '') return text diff --git a/jans-cli-tui/models/oauth/edit_client_dialog.py b/jans-cli-tui/models/oauth/edit_client_dialog.py index 572c70be644..67f512fd7bd 100644 --- a/jans-cli-tui/models/oauth/edit_client_dialog.py +++ b/jans-cli-tui/models/oauth/edit_client_dialog.py @@ -15,7 +15,6 @@ Button, Label, TextArea, - ) from cli import config_cli @@ -82,26 +81,45 @@ def cancel(): def prepare_tabs(self): + schema = self.myparent.cli_object.get_schema_from_reference('#/components/schemas/Client') self.tabs = OrderedDict() self.tabs['Basic'] = HSplit([ - self.myparent.getTitledText(title ="Client_ID", name='inum', value=self.data.get('inum',''), style='green'), - self.myparent.getTitledCheckBox("Active", name='disabled', checked= not self.data.get('disabled'), style='green'), - self.myparent.getTitledText("Client Name", name='displayName', value=self.data.get('displayName',''), style='green'), - self.myparent.getTitledText("Client Secret", name='clientSecret', value=self.data.get('clientSecret',''), style='green'), + self.myparent.getTitledText(title ="Client_ID", name='inum', value=self.data.get('inum',''), jans_help=self.myparent.get_help_from_schema(schema, 'inum'), style='green'), + self.myparent.getTitledCheckBox("Active", name='disabled', checked= not self.data.get('disabled'), jans_help=self.myparent.get_help_from_schema(schema, 'disabled'), style='green'), + self.myparent.getTitledText("Client Name", name='displayName', value=self.data.get('displayName',''), jans_help=self.myparent.get_help_from_schema(schema, 'displayName'), style='green'), + self.myparent.getTitledText("Client Secret", name='clientSecret', value=self.data.get('clientSecret',''), jans_help=self.myparent.get_help_from_schema(schema, 'clientSecret'), style='green'), self.myparent.getTitledText("Description", name='description', value=self.data.get('description',''), style='green'), self.myparent.getTitledWidget( - "Authn Method token endpoint", - name='tokenEndpointAuthMethodsSupported', - widget=DropDownWidget( - values=[('client_secret_basic', 'client_secret_basic'), ('client_secret_post', 'client_secret_post'), ('client_secret_jwt', 'client_secret_jwt'), ('private_key_jwt', 'private_key_jwt')], - value=self.data.get('tokenEndpointAuthMethodsSupported') + "Authn Method token endpoint", + name='tokenEndpointAuthMethodsSupported', + widget=DropDownWidget( + values=[('client_secret_basic', 'client_secret_basic'), ('client_secret_post', 'client_secret_post'), ('client_secret_jwt', 'client_secret_jwt'), ('private_key_jwt', 'private_key_jwt')], + value=self.data.get('tokenEndpointAuthMethodsSupported') + ), + jans_help=self.myparent.get_help_from_schema(schema, 'tokenEndpointAuthMethodsSupported'), + style='green' ), - style='green' - ), - self.myparent.getTitledRadioButton("Subject Type", name='subjectType', values=[('public', 'Public'),('pairwise', 'Pairwise')], current_value=self.data.get('subjectType'), style='green'), - self.myparent.getTitledCheckBoxList("Grant", name='grantTypes', values=[('authorization_code', 'Authorization Code'), ('refresh_token', 'Refresh Token'), ('urn:ietf:params:oauth:grant-type:uma-ticket', 'UMA Ticket'), ('client_credentials', 'Client Credentials'), ('password', 'Password'), ('implicit', 'Implicit')], current_values=self.data.get('grantTypes', []), style='green'), - self.myparent.getTitledCheckBoxList("Response Types", name='responseTypes', values=['code', 'token', 'id_token'], current_values=self.data.get('responseTypes', []), style='green'), + self.myparent.getTitledRadioButton( + "Subject Type", + name='subjectType', + values=[('public', 'Public'),('pairwise', 'Pairwise')], + current_value=self.data.get('subjectType'), + jans_help=self.myparent.get_help_from_schema(schema, 'subjectType'), + style='green'), + self.myparent.getTitledCheckBoxList( + "Grant", + name='grantTypes', + values=[('authorization_code', 'Authorization Code'), ('refresh_token', 'Refresh Token'), ('urn:ietf:params:oauth:grant-type:uma-ticket', 'UMA Ticket'), ('client_credentials', 'Client Credentials'), ('password', 'Password'), ('implicit', 'Implicit')], current_values=self.data.get('grantTypes', []), + jans_help=self.myparent.get_help_from_schema(schema, 'grantTypes'), + style='green'), + self.myparent.getTitledCheckBoxList( + "Response Types", + name='responseTypes', + values=['code', 'token', 'id_token'], + current_values=self.data.get('responseTypes', []), + jans_help=self.myparent.get_help_from_schema(schema, 'responseTypes'), + style='green'), self.myparent.getTitledCheckBox("Supress Authorization", name='dynamicRegistrationPersistClientAuthorizations', checked=self.data.get('dynamicRegistrationPersistClientAuthorizations'), style='green'), self.myparent.getTitledRadioButton("Application Type", name='applicationType', values=['native','web'], current_value=self.data.get('applicationType'), style='green'), self.myparent.getTitledText("Redirect Uris", name='redirectUris', value='\n'.join(self.data.get('redirectUris', [])), height=3, style='green'), @@ -263,7 +281,7 @@ def prepare_tabs(self): ],width=D() ) - + self.tabs['Client Scripts'] = HSplit([ # self.myparent.getTitledWidget( @@ -287,7 +305,7 @@ def prepare_tabs(self): ] ) - + self.left_nav = list(self.tabs.keys())[0] diff --git a/jans-cli-tui/wui_components/jans_dialog_with_nav.py b/jans-cli-tui/wui_components/jans_dialog_with_nav.py index d068e9e931e..e45618d2620 100644 --- a/jans-cli-tui/wui_components/jans_dialog_with_nav.py +++ b/jans-cli-tui/wui_components/jans_dialog_with_nav.py @@ -8,8 +8,12 @@ Dialog, ) + +from prompt_toolkit.layout import ScrollablePane + + class JansDialogWithNav(): - def __init__(self,content,height=None,width=None,title=None, button_functions=[], navbar=None): + def __init__(self,content, height=None, width=None, title=None, button_functions=[], navbar=None): self.navbar = navbar self.button_functions = button_functions self.title = title @@ -25,7 +29,7 @@ def create_window(self): self.dialog = Dialog( title=self.title, - body=VSplit([ + body=ScrollablePane(content=VSplit([ HSplit( [ self.navbar @@ -36,13 +40,9 @@ def create_window(self): Window(width=1, char="|",), HSplit([ self.content - - ]), - - + ], width=120, height=30)), - ],width=120,height=32), buttons=[ Button( text=str(self.button_functions[k][1]), @@ -50,7 +50,8 @@ def create_window(self): ) for k in range(len(self.button_functions)) ], with_background=False, - ) + + ) #--------------------------------------------------------------------------------------# def __pt_container__(self): return self.dialog diff --git a/jans-cli-tui/wui_components/jans_nav_bar.py b/jans-cli-tui/wui_components/jans_nav_bar.py index e91cc2be899..31829830a98 100644 --- a/jans-cli-tui/wui_components/jans_nav_bar.py +++ b/jans-cli-tui/wui_components/jans_nav_bar.py @@ -13,7 +13,6 @@ def __init__(self, myparent, entries, selection_changed, select=0, bgcolor='#00f self.selection_changed = selection_changed self.cur_tab = entries[self.cur_navbar_selection][0] self.create_window() - self.update_status_bar() def create_window(self): @@ -40,24 +39,15 @@ def get_navbar_entries(self): return merge_formatted_text(result) - def update_status_bar(self): - self.cur_tab = self.navbar_entries[self.cur_navbar_selection][0] - self.myparent.update_status_bar("Container for " + self.navbar_entries[self.cur_navbar_selection][1]) - if self.myparent.app_started: - self.selection_changed(self.cur_tab) - - def get_nav_bar_key_bindings(self): kb = KeyBindings() @kb.add("left") def _go_up(event) -> None: self.cur_navbar_selection = (self.cur_navbar_selection - 1) % len(self.navbar_entries) - self.update_status_bar() @kb.add("right") def _go_up(event) -> None: self.cur_navbar_selection = (self.cur_navbar_selection + 1) % len(self.navbar_entries) - self.update_status_bar() return kb diff --git a/jans-cli-tui/wui_components/jans_side_nav_bar.py b/jans-cli-tui/wui_components/jans_side_nav_bar.py index 78da832d5b9..e980a4b4333 100644 --- a/jans-cli-tui/wui_components/jans_side_nav_bar.py +++ b/jans-cli-tui/wui_components/jans_side_nav_bar.py @@ -18,7 +18,6 @@ def __init__(self, myparent, entries,selection_changed, select=0, entries_color= self.cur_tab = entries[self.cur_navbar_selection][0] self.selection_changed = selection_changed self.create_window() - self.update_status_bar() def create_window(self): self.side_Nav = FloatContainer( @@ -54,13 +53,7 @@ def get_navbar_entries(self): result.append("\n") result.append("\n") return merge_formatted_text(result) -#--------------------------------------------------------------------------------------# - def update_status_bar(self): - self.cur_tab = self.navbar_entries[self.cur_navbar_selection] - self.myparent.update_status_bar( - "Container for " + self.navbar_entries[self.cur_navbar_selection]) - self.selection_changed(self.cur_tab) -#--------------------------------------------------------------------------------------# + def get_nav_bar_key_bindings(self): kb = KeyBindings() @@ -69,13 +62,11 @@ def get_nav_bar_key_bindings(self): def _go_up(event) -> None: self.cur_navbar_selection = ( self.cur_navbar_selection - 1) % len(self.navbar_entries) - self.update_status_bar() @kb.add("down") def _go_up(event) -> None: self.cur_navbar_selection = ( self.cur_navbar_selection + 1) % len(self.navbar_entries) - self.update_status_bar() @kb.add("enter") def _(event): From 3f2350cfd54c257c3d6691bbfb757704df032a98 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Mon, 22 Aug 2022 21:39:24 +0300 Subject: [PATCH 048/364] fix: jans-cli dilog height --- .../wui_components/jans_dialog_with_nav.py | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/jans-cli-tui/wui_components/jans_dialog_with_nav.py b/jans-cli-tui/wui_components/jans_dialog_with_nav.py index e45618d2620..91d48b890c8 100644 --- a/jans-cli-tui/wui_components/jans_dialog_with_nav.py +++ b/jans-cli-tui/wui_components/jans_dialog_with_nav.py @@ -1,3 +1,6 @@ +from shutil import get_terminal_size + + from prompt_toolkit.layout.containers import ( HSplit, VSplit, @@ -9,7 +12,7 @@ ) -from prompt_toolkit.layout import ScrollablePane +from prompt_toolkit.layout import ScrollablePane, Layout class JansDialogWithNav(): @@ -25,23 +28,19 @@ def __init__(self,content, height=None, width=None, title=None, button_functions def create_window(self): max_data_str = 30 ## TODO TO BE Dynamic + wwidth, wheight = get_terminal_size() + height = 19 if wheight <= 30 else wheight - 11 self.dialog = Dialog( title=self.title, - body=ScrollablePane(content=VSplit([ - HSplit( - [ - self.navbar - ], - - width= (max_data_str ) #self.width - ), - Window(width=1, char="|",), - HSplit([ - self.content - ]), - ], width=120, height=30)), + body=VSplit([ + HSplit([ + self.navbar + ], width= (max_data_str )), + Window(width=1, char="|",), + ScrollablePane(content=self.content, height=height), + ], width=120), buttons=[ Button( From ab503aff6fe12a78bb2ccf11f9117d8a9050f9e9 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Tue, 23 Aug 2022 03:51:33 -0700 Subject: [PATCH 049/364] before merge 23-8-2022 --- jans-cli-tui/jans-cli-tui.py | 13 +--- .../models/oauth/edit_client_dialog.py | 63 +++++++++++++------ jans-cli-tui/models/oauth/oauth.py | 19 +----- 3 files changed, 45 insertions(+), 50 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index 1bafe6b3ec5..432832edcf7 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -6,12 +6,10 @@ import logging from shutil import get_terminal_size -import time -from asyncio import Future, ensure_future +from asyncio import ensure_future from pynput.keyboard import Key, Controller import prompt_toolkit -from prompt_toolkit.layout.margins import ScrollbarMargin from prompt_toolkit.application import Application from prompt_toolkit.application.current import get_app from prompt_toolkit.key_binding import KeyBindings @@ -20,11 +18,9 @@ from prompt_toolkit.formatted_text import HTML, merge_formatted_text from prompt_toolkit.layout.containers import ( - ConditionalContainer, Float, HSplit, VSplit, - VerticalAlign, HorizontalAlign, DynamicContainer, FloatContainer, @@ -35,26 +31,19 @@ from prompt_toolkit.layout.layout import Layout from prompt_toolkit.lexers import PygmentsLexer, DynamicLexer from prompt_toolkit.widgets import ( - Box, Button, Frame, Label, RadioList, TextArea, CheckboxList, - Shadow, Checkbox, ) -from prompt_toolkit.filters import Condition # -------------------------------------------------------------------------- # from cli import config_cli from wui_components.jans_cli_dialog import JansGDialog from wui_components.jans_nav_bar import JansNavBar -from wui_components.jans_side_nav_bar import JansSideNavBar -from wui_components.jans_vetrical_nav import JansVerticalNav -from wui_components.jans_dialog import JansDialog -from wui_components.jans_dialog_with_nav import JansDialogWithNav from cli_style import style diff --git a/jans-cli-tui/models/oauth/edit_client_dialog.py b/jans-cli-tui/models/oauth/edit_client_dialog.py index 572c70be644..a4d91adfb19 100644 --- a/jans-cli-tui/models/oauth/edit_client_dialog.py +++ b/jans-cli-tui/models/oauth/edit_client_dialog.py @@ -81,7 +81,18 @@ def cancel(): ) def prepare_tabs(self): - + with open('./hopa.log', 'a') as f: + f.write(str(self.data.get('tokenEndpointAuthMethodsSupported'))+'\n') + f.write(str(self.data.get('rptClaimsScripts'))+'\n') + f.write(str(self.data.get('claimRedirectUris')) +'\n') + f.write(str(self.data.get('authorizedAcrValues'))+'\n' ) + f.write(str(self.data.get('postAuthnScripts'))+'\n' ) + f.write(str(self.data.get('authorizedAcrValues'))+'\n' ) + f.write(str(self.data.get('updateTokenScriptDns'))+'\n' ) + f.write(str(self.data.get('introspectionScripts'))+'\n' ) + f.write(str(self.data.get('consentGatheringScripts'))+'\n' ) + f.write(str(self.data.get('dynamicRegistrationAllowedPasswordGrantScopes'))+'\n' ) + self.tabs = OrderedDict() self.tabs['Basic'] = HSplit([ @@ -265,25 +276,37 @@ def prepare_tabs(self): ) self.tabs['Client Scripts'] = HSplit([ - - # self.myparent.getTitledWidget( - # widget=DropDownWidget(values=[('spontaneousScopes','spontaneousScopes')]), - # ), - # self.myparent.getTitledWidget( - # widget=DropDownWidget(values=[('updateTokenScriptDns','updateTokenScriptDns')]), - # ), - # self.myparent.getTitledWidget( - # widget=DropDownWidget(values=[('postAuthnScripts','postAuthnScripts')]), - # ), - # self.myparent.getTitledWidget( - # widget=DropDownWidget(values=[('introspectionScripts','introspectionScripts')]), - # ), - # self.myparent.getTitledWidget( - # widget=DropDownWidget(values=[('dynamicRegistrationAllowedPasswordGrantScopes','dynamicRegistrationAllowedPasswordGrantScopes')]), - # ), - # self.myparent.getTitledWidget( - # widget=DropDownWidget(values=[('consentGatheringScripts','consentGatheringScripts')]), - # ), + + self.myparent.getTitledWidget( + title="Spontaneous Scopes", + name='authorizedAcrValues', + widget=DropDownWidget(values=[('spontaneousScopes','spontaneousScopes')]), + ), + self.myparent.getTitledWidget( + title="Update Token", + name='updateTokenScriptDns', + widget=DropDownWidget(values=[('updateTokenScriptDns','updateTokenScriptDns')]), + ), + self.myparent.getTitledWidget( + title="Post Authn", + name='postAuthnScripts', + widget=DropDownWidget(values=[('postAuthnScripts','postAuthnScripts')]), + ), + self.myparent.getTitledWidget( + title="Introspection", + name='introspectionScripts', + widget=DropDownWidget(values=[('introspectionScripts','introspectionScripts')]), + ), + self.myparent.getTitledWidget( + title="Password Grant", + name='dynamicRegistrationAllowedPasswordGrantScopes', + widget=DropDownWidget(values=[('dynamicRegistrationAllowedPasswordGrantScopes','dynamicRegistrationAllowedPasswordGrantScopes')]), + ), + self.myparent.getTitledWidget( + title="OAuth Consent", + name='consentGatheringScripts', + widget=DropDownWidget(values=[('consentGatheringScripts','consentGatheringScripts')]), + ), ] ) diff --git a/jans-cli-tui/models/oauth/oauth.py b/jans-cli-tui/models/oauth/oauth.py index 4f16aeb9d30..ec8b63a569d 100644 --- a/jans-cli-tui/models/oauth/oauth.py +++ b/jans-cli-tui/models/oauth/oauth.py @@ -1,37 +1,22 @@ from curses import window import threading - -from collections import OrderedDict -import json -from asyncio import Future, ensure_future +from asyncio import ensure_future import prompt_toolkit from prompt_toolkit.application.current import get_app from prompt_toolkit.key_binding import KeyBindings from prompt_toolkit.key_binding.bindings.focus import focus_next, focus_previous from prompt_toolkit.layout.containers import ( - ConditionalContainer, - Float, HSplit, VSplit, - VerticalAlign, HorizontalAlign, DynamicContainer, - FloatContainer, - Window ) from prompt_toolkit.layout.containers import VerticalAlign from prompt_toolkit.layout.dimension import D -from prompt_toolkit.layout.layout import Layout -from prompt_toolkit.lexers import PygmentsLexer ,DynamicLexer from prompt_toolkit.widgets import ( Box, Button, - Frame, Label, - RadioList, - TextArea, - CheckboxList, - Shadow, ) from prompt_toolkit.filters import Condition @@ -55,7 +40,6 @@ def initialize(self): self.oauth_prepare_containers() self.oauth_nav_selection_changed(self.oauth_navbar.navbar_entries[0][0]) - def oauth_prepare_containers(self): self.oauth_data_container = { @@ -98,7 +82,6 @@ def oauth_prepare_containers(self): height=D(), ) - def oauth_prepare_navbar(self): self.oauth_navbar = JansNavBar( self, From d92bc4d571913da7f0d6f8b305ced9f3c1a24ebc Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Tue, 23 Aug 2022 14:01:46 +0300 Subject: [PATCH 050/364] fix: jans-cli update selection --- jans-cli-tui/jans-cli-tui.py | 4 ++-- jans-cli-tui/models/oauth/edit_client_dialog.py | 2 +- jans-cli-tui/wui_components/jans_side_nav_bar.py | 9 ++++++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index 938b170fc55..c68242d4ffc 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -350,10 +350,10 @@ def handle_long_string (self,text,values,cb): return new_title , cd , width - def getTitledText(self, title, name, value='', height=1, jans_help='', width=None,style=''): + def getTitledText(self, title, name, value='', height=1, jans_help='', read_only=False, width=None, style=''): title += ': ' multiline = height > 1 - ta = TextArea(text=str(value), multiline=multiline,style="class:titledtext") + ta = TextArea(text=str(value), multiline=multiline, read_only=read_only, style="class:titledtext") ta.window.jans_name = name ta.window.jans_help = jans_help ta.window.me = ta diff --git a/jans-cli-tui/models/oauth/edit_client_dialog.py b/jans-cli-tui/models/oauth/edit_client_dialog.py index 67f512fd7bd..aefdc15caf8 100644 --- a/jans-cli-tui/models/oauth/edit_client_dialog.py +++ b/jans-cli-tui/models/oauth/edit_client_dialog.py @@ -85,7 +85,7 @@ def prepare_tabs(self): self.tabs = OrderedDict() self.tabs['Basic'] = HSplit([ - self.myparent.getTitledText(title ="Client_ID", name='inum', value=self.data.get('inum',''), jans_help=self.myparent.get_help_from_schema(schema, 'inum'), style='green'), + self.myparent.getTitledText(title ="Client_ID", name='inum', value=self.data.get('inum',''), jans_help=self.myparent.get_help_from_schema(schema, 'inum'), read_only=True, style='green'), self.myparent.getTitledCheckBox("Active", name='disabled', checked= not self.data.get('disabled'), jans_help=self.myparent.get_help_from_schema(schema, 'disabled'), style='green'), self.myparent.getTitledText("Client Name", name='displayName', value=self.data.get('displayName',''), jans_help=self.myparent.get_help_from_schema(schema, 'displayName'), style='green'), self.myparent.getTitledText("Client Secret", name='clientSecret', value=self.data.get('clientSecret',''), jans_help=self.myparent.get_help_from_schema(schema, 'clientSecret'), style='green'), diff --git a/jans-cli-tui/wui_components/jans_side_nav_bar.py b/jans-cli-tui/wui_components/jans_side_nav_bar.py index e980a4b4333..c99264878d0 100644 --- a/jans-cli-tui/wui_components/jans_side_nav_bar.py +++ b/jans-cli-tui/wui_components/jans_side_nav_bar.py @@ -10,7 +10,7 @@ class JansSideNavBar(): - def __init__(self, myparent, entries,selection_changed, select=0, entries_color='#00ff44'): + def __init__(self, myparent, entries, selection_changed, select=0, entries_color='#00ff44'): self.myparent = myparent # ListBox parent class self.navbar_entries = entries # ListBox entries self.cur_navbar_selection = select # ListBox initial selection @@ -55,6 +55,11 @@ def get_navbar_entries(self): return merge_formatted_text(result) + def update_selection(self): + self.cur_tab = self.navbar_entries[self.cur_navbar_selection] + self.selection_changed(self.cur_tab) + + def get_nav_bar_key_bindings(self): kb = KeyBindings() @@ -62,11 +67,13 @@ def get_nav_bar_key_bindings(self): def _go_up(event) -> None: self.cur_navbar_selection = ( self.cur_navbar_selection - 1) % len(self.navbar_entries) + self.update_selection() @kb.add("down") def _go_up(event) -> None: self.cur_navbar_selection = ( self.cur_navbar_selection + 1) % len(self.navbar_entries) + self.update_selection() @kb.add("enter") def _(event): From ad7412bdfd338f8fd9c7a727ddf04305d9ddb80e Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Tue, 23 Aug 2022 04:55:36 -0700 Subject: [PATCH 051/364] fix jans-cli block empty space select and easy navigate days added --- .../wui_components/jans_data_picker.py | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/jans-cli-tui/wui_components/jans_data_picker.py b/jans-cli-tui/wui_components/jans_data_picker.py index 7e756bbe2b5..49ac1dc2eb1 100644 --- a/jans-cli-tui/wui_components/jans_data_picker.py +++ b/jans-cli-tui/wui_components/jans_data_picker.py @@ -23,7 +23,7 @@ import calendar -#### not finished yet +#### not finished yet >> data class JansSelectDate: @@ -35,7 +35,7 @@ def __init__(self, date=''): self.cord_x = 0 self.extract_date(self.date) - #self.depug=Label(text="Cord_y = "+str(self.cord_y) +": current_month = "+ str(self.current_month),) + #self.depug=Label(text="entries = "+str(self.entries[self.cord_y][self.cord_x])+':',) self.month_label = Label(text=self.months[self.current_month-1],width=len(self.months[self.current_month-1])) self.year_label = Label(text=str(self.current_year),width=len(str(self.current_year))) @@ -117,62 +117,62 @@ def _get_formatted_text(self): return merge_formatted_text(result) - def inc_month(self): + def inc_month(self,day): if self.current_month != 12: self.current_month+=1 self.month_label = Label(text=self.months[self.current_month-1],width=len(self.months[self.current_month-1])) - current_date = '20/'+str(self.current_month) + '/'+str(self.current_year) + current_date = str(day)+'/'+str(self.current_month) + '/'+str(self.current_year) self.extract_date(current_date) - def dec_month(self): + def dec_month(self,day): if self.current_month > 1: self.current_month-=1 self.month_label = Label(text=self.months[self.current_month-1],width=len(self.months[self.current_month-1])) - current_date = '20/'+str(self.current_month) + '/'+str(self.current_year) + current_date = str(day)+'/'+str(self.current_month) + '/'+str(self.current_year) self.extract_date(current_date) - def inc_year(self): + def inc_year(self,day): self.current_year+=1 self.year_label = Label(text=str(self.current_year),width=len(str(self.current_year))) - current_date = '20/'+str(self.current_month) + '/'+str(self.current_year) + current_date = str(day)+'/'+str(self.current_month) + '/'+str(self.current_year) self.extract_date(current_date)# 20/2/1997 - def dec_year(self): + def dec_year(self,day): self.current_year-=1 self.year_label = Label(text=str(self.current_year),width=len(str(self.current_year))) - current_date = '20/'+str(self.current_month) + '/'+str(self.current_year) + current_date = str(day)+'/'+str(self.current_month) + '/'+str(self.current_year) self.extract_date(current_date)# 20/2/1997 def up(self): - if self.cord_y == 0: - self.dec_month() + if self.cord_y == 0 or int(self.entries[self.cord_y-1][self.cord_x]) == 0: + self.dec_month(day=1) else: self.cord_y = (self.cord_y - 1)# % 5 - #self.depug=Label(text="Cord_y = "+str(self.cord_y) +": current_month = "+ str(self.current_month),) + #self.depug=Label(text="entries = "+str(self.entries[self.cord_y][self.cord_x])+':',) def down(self): - if self.cord_y == 4: - self.inc_month() + if self.cord_y == 4 or int(self.entries[self.cord_y+1][self.cord_x]) == 0: + self.inc_month(day=28) else: self.cord_y = (self.cord_y + 1)# % 5 - #self.depug=Label(text="Cord_y = "+str(self.cord_y) +": current_month = "+ str(self.current_month),) + #self.depug=Label(text="entries = "+str(self.entries[self.cord_y][self.cord_x])+':',) def right(self): - if self.cord_x == 6: - self.inc_year() + if self.cord_x == 6 or int(self.entries[self.cord_y][self.cord_x+1]) == 0: + self.inc_year(day=7) else : self.cord_x = (self.cord_x + 1) #% 7 - #self.depug=Label(text="Cord_y = "+str(self.cord_y) +": current_month = "+ str(self.current_month) ,) + #self.depug=Label(text="entries = "+str(self.entries[self.cord_y][self.cord_x])+':',) def left(self): - if self.cord_x == 0: - self.dec_year() + if self.cord_x == 0 or int(self.entries[self.cord_y][self.cord_x-1]) == 0: + self.dec_year(day=1) else: self.cord_x = (self.cord_x - 1)# % 7 - #self.depug=Label(text="Cord_y = "+str(self.cord_y) +": current_month = "+ str(self.current_month),) + #self.depug=Label(text="entries = "+str(self.entries[self.cord_y][self.cord_x])+':',) def __pt_container__(self): @@ -215,7 +215,7 @@ def _focus_next(event): def _enter(event) -> None: if self.select_box_float not in get_app().layout.container.floats: - get_app().layout.container.floats.append( self.select_box_float) + get_app().layout.container.floats.append( self.select_box_float) else: self.text = str(self.select_box.entries[self.select_box.cord_y][self.select_box.cord_x] )+'/'+ str(self.select_box.months[self.select_box.current_month-1] ) +'/'+str(self.select_box.current_year) From 2f530e9800410424858b8b61a30ad69f61073eb9 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Tue, 23 Aug 2022 04:56:10 -0700 Subject: [PATCH 052/364] fix jans-cli merge --- .../wui_components/jans_dialog_with_nav.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/jans-cli-tui/wui_components/jans_dialog_with_nav.py b/jans-cli-tui/wui_components/jans_dialog_with_nav.py index 91d48b890c8..cb637615af8 100644 --- a/jans-cli-tui/wui_components/jans_dialog_with_nav.py +++ b/jans-cli-tui/wui_components/jans_dialog_with_nav.py @@ -1,6 +1,3 @@ -from shutil import get_terminal_size - - from prompt_toolkit.layout.containers import ( HSplit, VSplit, @@ -12,7 +9,7 @@ ) -from prompt_toolkit.layout import ScrollablePane, Layout +from prompt_toolkit.layout import ScrollablePane class JansDialogWithNav(): @@ -28,19 +25,23 @@ def __init__(self,content, height=None, width=None, title=None, button_functions def create_window(self): max_data_str = 30 ## TODO TO BE Dynamic - wwidth, wheight = get_terminal_size() - height = 19 if wheight <= 30 else wheight - 11 self.dialog = Dialog( title=self.title, - body=VSplit([ - HSplit([ - self.navbar - ], width= (max_data_str )), - Window(width=1, char="|",), - ScrollablePane(content=self.content, height=height), - ], width=120), + body=ScrollablePane(content=VSplit([ + HSplit( + [ + self.navbar + ], + + width= (max_data_str ) #self.width + ), + Window(width=1, char="|",), + HSplit([ + self.content + ]), + ], width=120, height=30)), buttons=[ Button( @@ -54,4 +55,3 @@ def create_window(self): #--------------------------------------------------------------------------------------# def __pt_container__(self): return self.dialog - From 90566e60b4da1a86913e36c00bedf20bd6290f33 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Tue, 23 Aug 2022 18:11:26 +0300 Subject: [PATCH 053/364] feat: jans-cli implement save client --- jans-cli-tui/cli/config_cli.py | 7 ++- jans-cli-tui/cli_style.py | 5 ++- jans-cli-tui/jans-cli-tui.py | 11 ++++- .../models/oauth/edit_client_dialog.py | 26 ++++++++--- jans-cli-tui/models/oauth/oauth.py | 39 +++++----------- jans-cli-tui/utils.py | 44 +++++++++++++++++++ .../wui_components/jans_dialog_with_nav.py | 26 +++++------ .../wui_components/jans_vetrical_nav.py | 6 +-- 8 files changed, 109 insertions(+), 55 deletions(-) create mode 100644 jans-cli-tui/utils.py diff --git a/jans-cli-tui/cli/config_cli.py b/jans-cli-tui/cli/config_cli.py index 59f88694608..7b152cf6697 100755 --- a/jans-cli-tui/cli/config_cli.py +++ b/jans-cli-tui/cli/config_cli.py @@ -1075,7 +1075,12 @@ def put_requests(self, endpoint, data): verify=self.verify_ssl, cert=self.mtls_client_cert ) + self.log_response(response) + + if self.wrapped: + return response + try: result = response.json() except Exception: @@ -1249,7 +1254,7 @@ def process_command_post(self, path, suffix_param, endpoint_params, data_fn, dat self.print_response(response) def process_command_put(self, path, suffix_param, endpoint_params, data_fn, data=None): - self.process_command_post(path, suffix_param, endpoint_params, data_fn, data=None) + return self.process_command_post(path, suffix_param, endpoint_params, data_fn, data) def process_command_patch(self, path, suffix_param, endpoint_params, data_fn, data=None): # TODO: suffix_param, endpoint_params diff --git a/jans-cli-tui/cli_style.py b/jans-cli-tui/cli_style.py index d1ec76f0376..9e707fc1196 100644 --- a/jans-cli-tui/cli_style.py +++ b/jans-cli-tui/cli_style.py @@ -12,10 +12,11 @@ "focused button": "bg:#880000 #ffffff noinherit", # Styling for Dialog widgets. "button-bar": "bg:#4D4D4D", - "text-area focused": "bg:#ff0000", + "textarea-readonly": "bg:#ffffff fg:#4D4D4D", + "required-field": "#8b000a", + "textarea":"bg:#ffffff fg:#0000ff", "status": "reverse", "select-box cursor-line": "nounderline bg:ansired fg:ansiwhite", - "textarea":"nounderline bg:#ffffff fg:#d1c0c0 #ff0000", "checkbox":"nounderline bg:#ffffff fg:#d1c0c0 #ff0000", } ) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index 9457feffb56..1432120f70b 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -6,7 +6,8 @@ import logging from shutil import get_terminal_size -from asyncio import ensure_future +import time +from asyncio import Future, ensure_future from pynput.keyboard import Key, Controller import prompt_toolkit @@ -342,7 +343,13 @@ def handle_long_string (self,text,values,cb): def getTitledText(self, title, name, value='', height=1, jans_help='', read_only=False, width=None, style=''): title += ': ' multiline = height > 1 - ta = TextArea(text=str(value), multiline=multiline, read_only=read_only, style="class:titledtext") + ta = TextArea( + text=str(value), + multiline=multiline, + read_only=read_only, + style="class:textarea-readonly" if read_only else "class:textarea", + focusable=not read_only, + ) ta.window.jans_name = name ta.window.jans_help = jans_help ta.window.me = ta diff --git a/jans-cli-tui/models/oauth/edit_client_dialog.py b/jans-cli-tui/models/oauth/edit_client_dialog.py index adab15c09e1..f67c6897d46 100644 --- a/jans-cli-tui/models/oauth/edit_client_dialog.py +++ b/jans-cli-tui/models/oauth/edit_client_dialog.py @@ -43,8 +43,9 @@ from wui_components.jans_cli_dialog import JansGDialog from wui_components.jans_drop_down import DropDownWidget from wui_components.jans_data_picker import DateSelectWidget +from utils import DialogUtils -class EditClientDialog(JansGDialog): +class EditClientDialog(JansGDialog, DialogUtils): def __init__(self, parent, title, data, buttons=[], save_handler=None): super().__init__(parent, title, buttons) self.save_handler = save_handler @@ -52,6 +53,19 @@ def __init__(self, parent, title, data, buttons=[], save_handler=None): self.prepare_tabs() def save(): + + self.data = self.make_data_from_dialog() + + for list_key in ('redirectUris', 'scopes'): + if self.data[list_key]: + self.data[list_key] = self.data[list_key].splitlines() + + cfr = self.check_required_fields() + self.myparent.logger.debug("CFR: "+str(cfr)) + if not cfr: + return + + self.myparent.logger.debug("handler: "+str(save_handler)) close_me = True if save_handler: close_me = self.save_handler(self) @@ -124,13 +138,13 @@ def prepare_tabs(self): style='green'), self.myparent.getTitledCheckBox("Supress Authorization", name='dynamicRegistrationPersistClientAuthorizations', checked=self.data.get('dynamicRegistrationPersistClientAuthorizations'), style='green'), self.myparent.getTitledRadioButton("Application Type", name='applicationType', values=['native','web'], current_value=self.data.get('applicationType'), style='green'), - self.myparent.getTitledText("Redirect Uris", name='redirectUris', value='\n'.join(self.data.get('redirectUris', [])), height=3, style='green'), + self.myparent.getTitledText("Redirect Uris", name='redirectUris', value='\n'.join(self.data.get('redirectUris', [])), height=3, style='class:required-field'), self.myparent.getTitledText("Redirect Regex", name='redirectUrisRegex', value=self.data.get('redirectUrisRegex', ''), style='green'), self.myparent.getTitledText("Scopes", name='scopes', value='\n'.join(self.data.get('scopes', [])), height=3, style='green'), ],width=D(), ) - + """ self.tabs['Tokens'] = HSplit([ self.myparent.getTitledRadioButton("Access Token Type", name='accessTokenAsJwt', values=['JWT','Reference'], current_value=self.data.get('accessTokenAsJwt'), style='green'), self.myparent.getTitledCheckBox("Incliude Claims in id_token", name='includeClaimsInIdToken', checked=self.data.get('includeClaimsInIdToken'), style='green'), @@ -293,12 +307,12 @@ def prepare_tabs(self): ), self.myparent.getTitledWidget( title="Update Token", - name='updateTokenScriptDns', + name='updateTokenScriptDns', widget=DropDownWidget(values=[('updateTokenScriptDns','updateTokenScriptDns')]), ), self.myparent.getTitledWidget( title="Post Authn", - name='postAuthnScripts', + name='postAuthnScripts', widget=DropDownWidget(values=[('postAuthnScripts','postAuthnScripts')]), ), self.myparent.getTitledWidget( @@ -319,7 +333,7 @@ def prepare_tabs(self): ] ) - + """ self.left_nav = list(self.tabs.keys())[0] diff --git a/jans-cli-tui/models/oauth/oauth.py b/jans-cli-tui/models/oauth/oauth.py index ec8b63a569d..1c1c4e33b5b 100644 --- a/jans-cli-tui/models/oauth/oauth.py +++ b/jans-cli-tui/models/oauth/oauth.py @@ -220,49 +220,32 @@ def edit_client_dialog(self, **params): selected_line_data = params['data'] title = "Edit user Data (Clients)" - dialog = EditClientDialog(self, title=title, data=selected_line_data) + dialog = EditClientDialog(self, title=title, data=selected_line_data, save_handler=self.save_client) self.show_jans_dialog(dialog) def save_client(self, dialog): - data = {} - for tab in dialog.tabs: - for item in dialog.tabs[tab].children: - if hasattr(item, 'children') and len(item.children)>1 and hasattr(item.children[1], 'jans_name'): - key_ = item.children[1].jans_name - self.logger.debug(key_ + ':' + str(type(item.children[1].me))) - if isinstance(item.children[1].me, prompt_toolkit.widgets.base.TextArea): - value_ = item.children[1].me.text - elif isinstance(item.children[1].me, prompt_toolkit.widgets.base.CheckboxList): - value_ = item.children[1].me.current_values - elif isinstance(item.children[1].me, prompt_toolkit.widgets.base.RadioList): - value_ = item.children[1].me.current_value - elif isinstance(item.children[1].me, prompt_toolkit.widgets.base.Checkbox): - value_ = item.children[1].me.checked - elif isinstance(item.children[1].me, DropDownWidget): - value_ = item.children[1].me.value - - data[key_] = value_ - - for list_key in ('redirectUris', 'scopes'): - if data[list_key]: - data[list_key] = data[list_key].splitlines() - - self.logger.debug(str(data)) + + self.logger.debug(dialog.data) response = self.cli_object.process_command_by_id( - operation_id='post-oauth-openid-clients', + operation_id='put-oauth-openid-clients' if dialog.data.get('inum') else 'post-oauth-openid-clients', url_suffix='', endpoint_args='', data_fn='', - data=data + data=dialog.data ) + + self.logger.debug(response.text) + if response.status_code in (200, 201): self.oauth_get_clients() return True + self.show_message("Error!", "An error ocurred while saving client:\n" + str(response.text)) + def add_client(self): - dialog = EditClientDialog(self, title="Add Client", data={'tokenEndpointAuthMethodsSupported':'private_key_jwt'}, save_handler=self.save_client) + dialog = EditClientDialog(self, title="Add Client", save_handler=self.save_client) result = self.show_jans_dialog(dialog) def delete_client(self, selected, event): diff --git a/jans-cli-tui/utils.py b/jans-cli-tui/utils.py new file mode 100644 index 00000000000..46170d34c76 --- /dev/null +++ b/jans-cli-tui/utils.py @@ -0,0 +1,44 @@ +import prompt_toolkit + + +from cli_style import style +from wui_components.jans_drop_down import DropDownWidget +from wui_components.jans_data_picker import DateSelectWidget + + + +class DialogUtils: + + def make_data_from_dialog(self): + data = {} + for tab in self.tabs: + for item in self.tabs[tab].children: + if hasattr(item, 'children') and len(item.children)>1 and hasattr(item.children[1], 'jans_name'): + key_ = item.children[1].jans_name + if isinstance(item.children[1].me, prompt_toolkit.widgets.base.TextArea): + value_ = item.children[1].me.text + elif isinstance(item.children[1].me, prompt_toolkit.widgets.base.CheckboxList): + value_ = item.children[1].me.current_values + elif isinstance(item.children[1].me, prompt_toolkit.widgets.base.RadioList): + value_ = item.children[1].me.current_value + elif isinstance(item.children[1].me, prompt_toolkit.widgets.base.Checkbox): + value_ = item.children[1].me.checked + elif isinstance(item.children[1].me, DropDownWidget): + value_ = item.children[1].me.value + data[key_] = value_ + + return data + + + def check_required_fields(self): + missing_fields = [] + for tab in self.tabs: + for item in self.tabs[tab].children: + if hasattr(item, 'children') and len(item.children)>1 and hasattr(item.children[1], 'jans_name'): + if 'required-field' in item.children[0].style and not self.data.get(item.children[1].jans_name, None): + missing_fields.append(item.children[0].content.text().strip().strip(':')) + if missing_fields: + self.myparent.show_message("Please fill required fields", "The following fields are required:\n" + ', '.join(missing_fields)) + return False + + return True diff --git a/jans-cli-tui/wui_components/jans_dialog_with_nav.py b/jans-cli-tui/wui_components/jans_dialog_with_nav.py index cb637615af8..5ac25c58b61 100644 --- a/jans-cli-tui/wui_components/jans_dialog_with_nav.py +++ b/jans-cli-tui/wui_components/jans_dialog_with_nav.py @@ -1,3 +1,6 @@ +from shutil import get_terminal_size + + from prompt_toolkit.layout.containers import ( HSplit, VSplit, @@ -25,23 +28,19 @@ def __init__(self,content, height=None, width=None, title=None, button_functions def create_window(self): max_data_str = 30 ## TODO TO BE Dynamic + wwidth, wheight = get_terminal_size() + height = 19 if wheight <= 30 else wheight - 11 self.dialog = Dialog( title=self.title, - body=ScrollablePane(content=VSplit([ - HSplit( - [ - self.navbar - ], - - width= (max_data_str ) #self.width - ), - Window(width=1, char="|",), - HSplit([ - self.content - ]), - ], width=120, height=30)), + body=VSplit([ + HSplit([ + self.navbar + ], width= (max_data_str )), + Window(width=1, char="|",), + ScrollablePane(content=self.content, height=height), + ], width=120), buttons=[ Button( @@ -55,3 +54,4 @@ def create_window(self): #--------------------------------------------------------------------------------------# def __pt_container__(self): return self.dialog + diff --git a/jans-cli-tui/wui_components/jans_vetrical_nav.py b/jans-cli-tui/wui_components/jans_vetrical_nav.py index 8c3c80dc6bb..64587e50b51 100644 --- a/jans-cli-tui/wui_components/jans_vetrical_nav.py +++ b/jans-cli-tui/wui_components/jans_vetrical_nav.py @@ -161,7 +161,7 @@ def _(event): self.on_enter(passed=passed,event=event,size=size,data=self.all_data[self.selectes]) - @kb.add("j") + @kb.add("d") def _(event): selected_line = [i.strip() for i in self.data[self.selectes]] size = self.myparent.output.get_size() @@ -173,7 +173,7 @@ def _(event): data=self.all_data[self.selectes]) - @kb.add("d") + @kb.add("delete") def _(event): if self.on_delete: selected_line = [i.strip() for i in self.data[self.selectes]] @@ -187,4 +187,4 @@ def _(event): # -------------------------------------------------------------------------------- # def __pt_container__(self): - return self.container \ No newline at end of file + return self.container From 5ecfc2e70fa2ff798561e9b3e18b93ce24907c2a Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Tue, 23 Aug 2022 21:27:31 +0300 Subject: [PATCH 054/364] feat: jans-cli implement searching client --- jans-cli-tui/jans-cli-tui.py | 5 ++- jans-cli-tui/models/oauth/oauth.py | 70 ++++++++++++++++++++---------- 2 files changed, 49 insertions(+), 26 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index 1432120f70b..c0d5cbe42be 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -340,7 +340,7 @@ def handle_long_string (self,text,values,cb): return new_title , cd , width - def getTitledText(self, title, name, value='', height=1, jans_help='', read_only=False, width=None, style=''): + def getTitledText(self, title, name, value='', height=1, jans_help='', accept_handler=None, read_only=False, width=None, style=''): title += ': ' multiline = height > 1 ta = TextArea( @@ -349,6 +349,7 @@ def getTitledText(self, title, name, value='', height=1, jans_help='', read_only read_only=read_only, style="class:textarea-readonly" if read_only else "class:textarea", focusable=not read_only, + accept_handler=accept_handler, ) ta.window.jans_name = name ta.window.jans_help = jans_help @@ -466,7 +467,7 @@ async def coroutine(): return result ensure_future(coroutine()) - + def data_display_dialog(self, **params): body = HSplit([ diff --git a/jans-cli-tui/models/oauth/oauth.py b/jans-cli-tui/models/oauth/oauth.py index 1c1c4e33b5b..78bbd180ffa 100644 --- a/jans-cli-tui/models/oauth/oauth.py +++ b/jans-cli-tui/models/oauth/oauth.py @@ -64,7 +64,7 @@ def oauth_prepare_containers(self): self.oauth_containers['clients'] = HSplit([ VSplit([ self.getButton(text="Get Clients", name='oauth:clients:get', jans_help="Retreive first 10 OpenID Connect clients", handler=self.oauth_get_clients), - self.getTitledText('Search', name='oauth:clients:search', jans_help='Press enter to perform search'), + self.getTitledText('Search', name='oauth:clients:search', jans_help='Press enter to perform search', accept_handler=self.search_clients), self.getButton(text="Add Client", name='oauth:clients:add', jans_help="To add a new client press this button", handler=self.add_client), ], @@ -100,10 +100,20 @@ def oauth_nav_selection_changed(self, selection): def oauth_set_center_frame(self): self.center_container = self.oauth_main_container - def oauth_update_clients(self): + def oauth_update_clients(self, pattern=''): + endpoint_args='limit:10' + if pattern: + endpoint_args='limit:10,pattern:'+pattern try : - rsponse = self.cli_object.process_command_by_id('get-oauth-openid-clients', '', 'limit:10', {}) + rsponse = self.cli_object.process_command_by_id( + operation_id='get-oauth-openid-clients', + url_suffix='', + endpoint_args=endpoint_args, + data_fn=None, + data={} + ) + except Exception as e: self.show_message("Error getting clients", str(e)) return @@ -131,27 +141,30 @@ def oauth_update_clients(self): ] ) - clients = JansVerticalNav( - myparent=self, - headers=['Client ID', 'Client Name', 'Grant Types', 'Subject Type'], - preferred_size= [0,0,30,0], - data=data, - on_enter=self.edit_client_dialog, - on_display=self.data_display_dialog, - on_delete=self.delete_client, - # selection_changed=self.data_selection_changed, - selectes=0, - headerColor='green', - entriesColor='white', - all_data=result - ) + if data: + clients = JansVerticalNav( + myparent=self, + headers=['Client ID', 'Client Name', 'Grant Types', 'Subject Type'], + preferred_size= [0,0,30,0], + data=data, + on_enter=self.edit_client_dialog, + on_display=self.data_display_dialog, + on_delete=self.delete_client, + # selection_changed=self.data_selection_changed, + selectes=0, + headerColor='green', + entriesColor='white', + all_data=result + ) - self.layout.focus(clients) # clients.focuse..!? TODO >> DONE - self.oauth_data_container['clients'] = HSplit([ - clients - ]) + self.layout.focus(clients) # clients.focuse..!? TODO >> DONE + self.oauth_data_container['clients'] = HSplit([ + clients + ]) + get_app().invalidate() - get_app().invalidate() + else: + self.show_message("Oops", "No matching result") def oauth_get_clients(self): @@ -224,7 +237,6 @@ def edit_client_dialog(self, **params): self.show_jans_dialog(dialog) def save_client(self, dialog): - self.logger.debug(dialog.data) @@ -244,8 +256,18 @@ def save_client(self, dialog): self.show_message("Error!", "An error ocurred while saving client:\n" + str(response.text)) + def search_clients(self, tbuffer): + if not len(tbuffer.text) > 2: + self.show_message("Error!", "Search string should be at least three characters") + return + + t = threading.Thread(target=self.oauth_update_clients, args=(tbuffer.text,), daemon=True) + t.start() + + + def add_client(self): - dialog = EditClientDialog(self, title="Add Client", save_handler=self.save_client) + dialog = EditClientDialog(self, title="Add Client", data={}, save_handler=self.save_client) result = self.show_jans_dialog(dialog) def delete_client(self, selected, event): From 60a5b9646cabcf734beacef8ea6c7bbd250fb1e5 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Tue, 23 Aug 2022 22:15:22 +0300 Subject: [PATCH 055/364] fix: jans-cli message display in threads --- jans-cli-tui/jans-cli-tui.py | 15 ++++++- jans-cli-tui/models/oauth/oauth.py | 1 + .../wui_components/jans_message_dialog.py | 40 +++++++++++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 jans-cli-tui/wui_components/jans_message_dialog.py diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index c0d5cbe42be..fccf202b986 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -45,6 +45,7 @@ from cli import config_cli from wui_components.jans_cli_dialog import JansGDialog from wui_components.jans_nav_bar import JansNavBar +from wui_components.jans_message_dialog import JansMessageDialog from cli_style import style @@ -504,9 +505,16 @@ def edit_scope_dialog(self, **params): def show_message(self, title, message, buttons=[]): body = HSplit([Label(message)]) - dialog = JansGDialog(self, title=title, body=body, buttons=buttons) - self.show_jans_dialog(dialog) + dialog = JansMessageDialog(title=title, body=body, buttons=buttons) + app = get_app() + focused_before = app.layout.current_window + float_ = Float(content=dialog) + self.root_layout.floats.insert(0, float_) + dialog.me = float_ + dialog.focus_on_exit = focused_before + app.layout.focus(dialog) + self.press_tab() def show_again(self): ## nasted dialog Button self.show_message("Again", "Nasted Dialogs",) @@ -518,6 +526,9 @@ def get_confirm_dialog(self, message): dialog = JansGDialog(self, title="Confirmation", body=body, buttons=buttons) return dialog + + + application = JansCliApp() def run(): diff --git a/jans-cli-tui/models/oauth/oauth.py b/jans-cli-tui/models/oauth/oauth.py index 78bbd180ffa..ff215fe26cc 100644 --- a/jans-cli-tui/models/oauth/oauth.py +++ b/jans-cli-tui/models/oauth/oauth.py @@ -126,6 +126,7 @@ def oauth_update_clients(self, pattern=''): result = rsponse.json() except Exception: self.show_message("Error getting clients", str(rsponse.text)) + press_tab return diff --git a/jans-cli-tui/wui_components/jans_message_dialog.py b/jans-cli-tui/wui_components/jans_message_dialog.py new file mode 100644 index 00000000000..a3c4cafb83d --- /dev/null +++ b/jans-cli-tui/wui_components/jans_message_dialog.py @@ -0,0 +1,40 @@ +from functools import partial + +from prompt_toolkit.widgets import Button, Dialog, Label +from prompt_toolkit.layout.containers import HSplit +from prompt_toolkit.application.current import get_app +from prompt_toolkit.layout.dimension import D + + +class JansMessageDialog: + def __init__(self, title, body, buttons=[], focus_on_exit=None): + self.result = None + self.me = None + self.focus_on_exit = focus_on_exit + if not buttons: + buttons = ["OK"] + + def exit_me(result): + self.result = result + app = get_app() + app.layout.focus(self.focus_on_exit) + if self.me in app.root_layout.floats: + app.root_layout.floats.remove(self.me) + + blist = [] + + for b in buttons: + handler = partial(exit_me, b) + button = Button(text=b, handler=handler) + blist.append(button) + + self.dialog = Dialog( + title=title, + body=body, + buttons=blist, + width=D(preferred=80), + modal=True, + ) + + def __pt_container__(self): + return self.dialog From 45d314cfd5c8d6ee62f9441ddfb7b45e82cec488 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Wed, 24 Aug 2022 10:54:36 +0300 Subject: [PATCH 056/364] fix: jans-cli append dialog instead of insert --- jans-cli-tui/jans-cli-tui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index fccf202b986..10b6ec9075d 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -510,7 +510,7 @@ def show_message(self, title, message, buttons=[]): app = get_app() focused_before = app.layout.current_window float_ = Float(content=dialog) - self.root_layout.floats.insert(0, float_) + self.root_layout.floats.append(float_) dialog.me = float_ dialog.focus_on_exit = focused_before app.layout.focus(dialog) From 6bd3a98f69fe137ee8dfea59162b9d9a4e26197f Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Wed, 24 Aug 2022 11:12:01 +0300 Subject: [PATCH 057/364] fix: jans-cli code cleaning --- .../models/oauth/edit_client_dialog.py | 31 +++---------------- jans-cli-tui/models/oauth/oauth.py | 4 +-- .../wui_components/jans_message_dialog.py | 1 - .../wui_components/jans_side_nav_bar.py | 4 +-- .../wui_components/jans_vetrical_nav.py | 1 - 5 files changed, 7 insertions(+), 34 deletions(-) diff --git a/jans-cli-tui/models/oauth/edit_client_dialog.py b/jans-cli-tui/models/oauth/edit_client_dialog.py index f67c6897d46..2da1c9b5fa7 100644 --- a/jans-cli-tui/models/oauth/edit_client_dialog.py +++ b/jans-cli-tui/models/oauth/edit_client_dialog.py @@ -1,43 +1,20 @@ -import json -from asyncio import Future from typing import OrderedDict -from prompt_toolkit.widgets import Button, TextArea -from prompt_toolkit.application.current import get_app from prompt_toolkit.layout.dimension import D -from static import DialogResult -from wui_components.jans_dialog import JansDialog -from prompt_toolkit.layout.containers import ( - VSplit, - DynamicContainer, -) -from prompt_toolkit.widgets import ( - Button, - Label, - TextArea, -) - -from cli import config_cli from prompt_toolkit.layout.containers import ( - ConditionalContainer, - Float, HSplit, VSplit, - VerticalAlign, DynamicContainer, - FloatContainer, Window ) from prompt_toolkit.widgets import ( Box, Button, - Frame, Label, - RadioList, - TextArea, - CheckboxList, - Shadow, ) + +from cli import config_cli +from static import DialogResult from wui_components.jans_dialog_with_nav import JansDialogWithNav from wui_components.jans_side_nav_bar import JansSideNavBar from wui_components.jans_cli_dialog import JansGDialog @@ -327,7 +304,7 @@ def prepare_tabs(self): ), self.myparent.getTitledWidget( title="OAuth Consent", - name='consentGatheringScripts', + name='consentGatheringScripts', widget=DropDownWidget(values=[('consentGatheringScripts','consentGatheringScripts')]), ), diff --git a/jans-cli-tui/models/oauth/oauth.py b/jans-cli-tui/models/oauth/oauth.py index ff215fe26cc..6a86e4e7f29 100644 --- a/jans-cli-tui/models/oauth/oauth.py +++ b/jans-cli-tui/models/oauth/oauth.py @@ -1,6 +1,6 @@ -from curses import window import threading from asyncio import ensure_future + import prompt_toolkit from prompt_toolkit.application.current import get_app from prompt_toolkit.key_binding import KeyBindings @@ -11,14 +11,12 @@ HorizontalAlign, DynamicContainer, ) -from prompt_toolkit.layout.containers import VerticalAlign from prompt_toolkit.layout.dimension import D from prompt_toolkit.widgets import ( Box, Button, Label, ) -from prompt_toolkit.filters import Condition from cli import config_cli from wui_components.jans_nav_bar import JansNavBar diff --git a/jans-cli-tui/wui_components/jans_message_dialog.py b/jans-cli-tui/wui_components/jans_message_dialog.py index a3c4cafb83d..a5a832b21b4 100644 --- a/jans-cli-tui/wui_components/jans_message_dialog.py +++ b/jans-cli-tui/wui_components/jans_message_dialog.py @@ -1,7 +1,6 @@ from functools import partial from prompt_toolkit.widgets import Button, Dialog, Label -from prompt_toolkit.layout.containers import HSplit from prompt_toolkit.application.current import get_app from prompt_toolkit.layout.dimension import D diff --git a/jans-cli-tui/wui_components/jans_side_nav_bar.py b/jans-cli-tui/wui_components/jans_side_nav_bar.py index c99264878d0..2b7d2946067 100644 --- a/jans-cli-tui/wui_components/jans_side_nav_bar.py +++ b/jans-cli-tui/wui_components/jans_side_nav_bar.py @@ -20,7 +20,7 @@ def __init__(self, myparent, entries, selection_changed, select=0, entries_color self.create_window() def create_window(self): - self.side_Nav = FloatContainer( + self.side_nav = FloatContainer( content=HSplit([ Window( content=FormattedTextControl( @@ -82,4 +82,4 @@ def _(event): return kb def __pt_container__(self): - return self.side_Nav + return self.side_nav diff --git a/jans-cli-tui/wui_components/jans_vetrical_nav.py b/jans-cli-tui/wui_components/jans_vetrical_nav.py index 64587e50b51..da4a76707cd 100644 --- a/jans-cli-tui/wui_components/jans_vetrical_nav.py +++ b/jans-cli-tui/wui_components/jans_vetrical_nav.py @@ -1,4 +1,3 @@ -from prompt_toolkit.widgets import TextArea from prompt_toolkit.layout.containers import HSplit, Window, FloatContainer from prompt_toolkit.layout.controls import FormattedTextControl from prompt_toolkit.layout.margins import ScrollbarMargin From d68ff6aef461ac77e3278dce91540e0ea8c7b0db Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Wed, 24 Aug 2022 02:39:38 -0700 Subject: [PATCH 058/364] fix jans-cli merging --- jans-cli-tui/jans-cli-tui.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index fccf202b986..8a58ad93969 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -343,6 +343,7 @@ def handle_long_string (self,text,values,cb): def getTitledText(self, title, name, value='', height=1, jans_help='', accept_handler=None, read_only=False, width=None, style=''): title += ': ' + num_lines = min(len(str(value).split("\n")),height) multiline = height > 1 ta = TextArea( text=str(value), @@ -355,7 +356,7 @@ def getTitledText(self, title, name, value='', height=1, jans_help='', accept_ha ta.window.jans_name = name ta.window.jans_help = jans_help ta.window.me = ta - li, cd, width = self.handle_long_string(title,[1]*height,ta) + li, cd, width = self.handle_long_string(title,[1]*num_lines,ta) return VSplit([Label(text=li, width=width,style=style), cd], padding=1) @@ -500,8 +501,9 @@ def save_creds(self, dialog): def edit_scope_dialog(self, **params): - dialog = EditScopeDialog(self,**params) - self.show_jans_dialog(dialog) + # dialog = EditScopeDialog(self,**params) + # self.show_jans_dialog(dialog) + pass def show_message(self, title, message, buttons=[]): body = HSplit([Label(message)]) From 5a0dbd037b00b332a87becd2bb5523ef81fe7891 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Wed, 24 Aug 2022 02:40:11 -0700 Subject: [PATCH 059/364] fix jans-cli merging --- .../models/oauth/edit_client_dialog.py | 532 +++++++++--------- 1 file changed, 277 insertions(+), 255 deletions(-) diff --git a/jans-cli-tui/models/oauth/edit_client_dialog.py b/jans-cli-tui/models/oauth/edit_client_dialog.py index f67c6897d46..cee591d82f8 100644 --- a/jans-cli-tui/models/oauth/edit_client_dialog.py +++ b/jans-cli-tui/models/oauth/edit_client_dialog.py @@ -1,43 +1,20 @@ -import json -from asyncio import Future from typing import OrderedDict -from prompt_toolkit.widgets import Button, TextArea -from prompt_toolkit.application.current import get_app from prompt_toolkit.layout.dimension import D -from static import DialogResult -from wui_components.jans_dialog import JansDialog -from prompt_toolkit.layout.containers import ( - VSplit, - DynamicContainer, -) -from prompt_toolkit.widgets import ( - Button, - Label, - TextArea, -) - -from cli import config_cli from prompt_toolkit.layout.containers import ( - ConditionalContainer, - Float, HSplit, VSplit, - VerticalAlign, DynamicContainer, - FloatContainer, Window ) from prompt_toolkit.widgets import ( Box, Button, - Frame, Label, - RadioList, - TextArea, - CheckboxList, - Shadow, ) + +from cli import config_cli +from static import DialogResult from wui_components.jans_dialog_with_nav import JansDialogWithNav from wui_components.jans_side_nav_bar import JansSideNavBar from wui_components.jans_cli_dialog import JansGDialog @@ -95,246 +72,289 @@ def cancel(): def prepare_tabs(self): - - schema = self.myparent.cli_object.get_schema_from_reference('#/components/schemas/Client') - - self.tabs = OrderedDict() - - self.tabs['Basic'] = HSplit([ - self.myparent.getTitledText(title ="Client_ID", name='inum', value=self.data.get('inum',''), jans_help=self.myparent.get_help_from_schema(schema, 'inum'), read_only=True, style='green'), - self.myparent.getTitledCheckBox("Active", name='disabled', checked= not self.data.get('disabled'), jans_help=self.myparent.get_help_from_schema(schema, 'disabled'), style='green'), - self.myparent.getTitledText("Client Name", name='displayName', value=self.data.get('displayName',''), jans_help=self.myparent.get_help_from_schema(schema, 'displayName'), style='green'), - self.myparent.getTitledText("Client Secret", name='clientSecret', value=self.data.get('clientSecret',''), jans_help=self.myparent.get_help_from_schema(schema, 'clientSecret'), style='green'), - self.myparent.getTitledText("Description", name='description', value=self.data.get('description',''), style='green'), - self.myparent.getTitledWidget( - "Authn Method token endpoint", - name='tokenEndpointAuthMethodsSupported', - widget=DropDownWidget( - values=[('client_secret_basic', 'client_secret_basic'), ('client_secret_post', 'client_secret_post'), ('client_secret_jwt', 'client_secret_jwt'), ('private_key_jwt', 'private_key_jwt')], - value=self.data.get('tokenEndpointAuthMethodsSupported') + self.myparent.logger.debug("Data: "+str(self.data)) + schema = self.myparent.cli_object.get_schema_from_reference('#/components/schemas/Client') + + self.tabs = OrderedDict() + + self.tabs['Basic'] = HSplit([ + self.myparent.getTitledText( + title ="Client_ID", + name='inum', + value=self.data.get('inum',''), + jans_help=self.myparent.get_help_from_schema(schema, 'inum'), + read_only=True, + style='green'), + self.myparent.getTitledCheckBox("Active", name='disabled', checked= not self.data.get('disabled'), jans_help=self.myparent.get_help_from_schema(schema, 'disabled'), style='green'), + self.myparent.getTitledText("Client Name", name='displayName', value=self.data.get('displayName',''), jans_help=self.myparent.get_help_from_schema(schema, 'displayName'), style='green'), + self.myparent.getTitledText("Client Secret", name='clientSecret', value=self.data.get('clientSecret',''), jans_help=self.myparent.get_help_from_schema(schema, 'clientSecret'), style='green'), + self.myparent.getTitledText("Description", name='description', value=self.data.get('description',''), style='green'), + self.myparent.getTitledWidget( + "Authn Method token endpoint", + name='tokenEndpointAuthMethodsSupported', + widget=DropDownWidget( + values=[('client_secret_basic', 'client_secret_basic'), ('client_secret_post', 'client_secret_post'), ('client_secret_jwt', 'client_secret_jwt'), ('private_key_jwt', 'private_key_jwt')], + value=self.data.get('tokenEndpointAuthMethodsSupported') + ), + jans_help=self.myparent.get_help_from_schema(schema, 'tokenEndpointAuthMethodsSupported'), + style='green' ), - jans_help=self.myparent.get_help_from_schema(schema, 'tokenEndpointAuthMethodsSupported'), - style='green' - ), - self.myparent.getTitledRadioButton( - "Subject Type", - name='subjectType', - values=[('public', 'Public'),('pairwise', 'Pairwise')], - current_value=self.data.get('subjectType'), - jans_help=self.myparent.get_help_from_schema(schema, 'subjectType'), + self.myparent.getTitledRadioButton( + "Subject Type", + name='subjectType', + values=[('public', 'Public'),('pairwise', 'Pairwise')], + current_value=self.data.get('subjectType'), + jans_help=self.myparent.get_help_from_schema(schema, 'subjectType'), + style='green'), + self.myparent.getTitledCheckBoxList( + "Grant", + name='grantTypes', + values=[('authorization_code', 'Authorization Code'), ('refresh_token', 'Refresh Token'), ('urn:ietf:params:oauth:grant-type:uma-ticket', 'UMA Ticket'), ('client_credentials', 'Client Credentials'), ('password', 'Password'), ('implicit', 'Implicit')], + current_values=self.data.get('grantTypes', []), + jans_help=self.myparent.get_help_from_schema(schema, 'grantTypes'), + style='green'), + self.myparent.getTitledCheckBoxList( + "Response Types", + name='responseTypes', + values=['code', 'token', 'id_token'], + current_values=self.data.get('responseTypes', []), + jans_help=self.myparent.get_help_from_schema(schema, 'responseTypes'), + style='green'), + self.myparent.getTitledCheckBox("Supress Authorization", + name='dynamicRegistrationPersistClientAuthorizations', + checked=self.data.get('dynamicRegistrationPersistClientAuthorizations'), + style='green'), + self.myparent.getTitledRadioButton("Application Type", name='applicationType', values=['native','web'], current_value=self.data.get('applicationType'), style='green'), + self.myparent.getTitledText("Redirect Uris", name='redirectUris', value='\n'.join(self.data.get('redirectUris', [])), height=3, style='class:required-field'), + self.myparent.getTitledText("Redirect Regex", name='redirectUrisRegex', value=self.data.get('redirectUrisRegex', ''), style='green'), + self.myparent.getTitledText("Scopes", + name='scopes', + value='\n'.join(self.data.get('scopes', [])), + height=3, + style='green'), + + ],width=D(), + ) + + self.tabs['Tokens'] = HSplit([ + # self.myparent.getTitledRadioButton( + # "Access Token Type", + # name='accessTokenAsJwt', + # values=['JWT','Reference'], + # current_value=self.data.get('accessTokenAsJwt'), + # jans_help=self.myparent.get_help_from_schema(schema, 'accessTokenAsJwt'), + # style='green'), + + self.myparent.getTitledCheckBox( + "Access Token Type", + name='accessTokenAsJwt', + checked=self.data.get('accessTokenAsJwt'), + style='green'), + + self.myparent.getTitledCheckBox( + "Incliude Claims in id_token", + name='includeClaimsInIdToken', + checked=self.data.get('includeClaimsInIdToken'), style='green'), - self.myparent.getTitledCheckBoxList( - "Grant", - name='grantTypes', - values=[('authorization_code', 'Authorization Code'), ('refresh_token', 'Refresh Token'), ('urn:ietf:params:oauth:grant-type:uma-ticket', 'UMA Ticket'), ('client_credentials', 'Client Credentials'), ('password', 'Password'), ('implicit', 'Implicit')], current_values=self.data.get('grantTypes', []), - jans_help=self.myparent.get_help_from_schema(schema, 'grantTypes'), + self.myparent.getTitledCheckBox( + "Run introspection script before JWT access token creation", + name='runIntrospectionScriptBeforeAccessTokenAsJwtCreationAndIncludeClaims', + checked=self.data.get('runIntrospectionScriptBeforeAccessTokenAsJwtCreationAndIncludeClaims'), style='green'), - self.myparent.getTitledCheckBoxList( - "Response Types", - name='responseTypes', - values=['code', 'token', 'id_token'], - current_values=self.data.get('responseTypes', []), - jans_help=self.myparent.get_help_from_schema(schema, 'responseTypes'), + self.myparent.getTitledText( + title="Token binding confirmation method for id_token", + name='idTokenTokenBindingCnf', + value=self.data.get('idTokenTokenBindingCnf',''), style='green'), - self.myparent.getTitledCheckBox("Supress Authorization", name='dynamicRegistrationPersistClientAuthorizations', checked=self.data.get('dynamicRegistrationPersistClientAuthorizations'), style='green'), - self.myparent.getTitledRadioButton("Application Type", name='applicationType', values=['native','web'], current_value=self.data.get('applicationType'), style='green'), - self.myparent.getTitledText("Redirect Uris", name='redirectUris', value='\n'.join(self.data.get('redirectUris', [])), height=3, style='class:required-field'), - self.myparent.getTitledText("Redirect Regex", name='redirectUrisRegex', value=self.data.get('redirectUrisRegex', ''), style='green'), - self.myparent.getTitledText("Scopes", name='scopes', value='\n'.join(self.data.get('scopes', [])), height=3, style='green'), - - ],width=D(), - ) - """ - self.tabs['Tokens'] = HSplit([ - self.myparent.getTitledRadioButton("Access Token Type", name='accessTokenAsJwt', values=['JWT','Reference'], current_value=self.data.get('accessTokenAsJwt'), style='green'), - self.myparent.getTitledCheckBox("Incliude Claims in id_token", name='includeClaimsInIdToken', checked=self.data.get('includeClaimsInIdToken'), style='green'), - self.myparent.getTitledCheckBox("Run introspection script before JWT access token creation", name='runIntrospectionScriptBeforeAccessTokenAsJwtCreationAndIncludeClaims', checked=self.data.get('runIntrospectionScriptBeforeAccessTokenAsJwtCreationAndIncludeClaims'), style='green'), - self.myparent.getTitledText(title="Token binding confirmation method for id_token", name='idTokenTokenBindingCnf',value=self.data.get('idTokenTokenBindingCnf',''), style='green'), - self.myparent.getTitledText(title="Access token additional audiences", name='additionalAudience',value=self.data.get('additionalAudience',''), style='green'), - VSplit([ - Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3) - ]), - self.myparent.getTitledText("Access token lifetime", name='accessTokenLifetime', value=self.data.get('accessTokenLifetime',''),style='green'), - self.myparent.getTitledText("Refresh token lifetime", name='refreshTokenLifetime', value=self.data.get('refreshTokenLifetime',''),style='green'), - self.myparent.getTitledText("Defult max authn age", name='defaultMaxAge', value=self.data.get('defaultMaxAge',''),style='green'), - - ],width=D()) - - self.tabs['Logout'] = HSplit([ - - self.myparent.getTitledText("Front channel logout URI", name='frontChannelLogoutUri', value=self.data.get('frontChannelLogoutUri',''),style='green'), - self.myparent.getTitledText("Post logout redirect URI", name='postLogoutRedirectUris', value=self.data.get('postLogoutRedirectUris',''),style='green'), - self.myparent.getTitledText("Back channel logout URI", name='backchannelLogoutUri', value=self.data.get('backchannelLogoutUri',''),style='green'), - self.myparent.getTitledCheckBox("Back channel logout session required", name='backchannelLogoutSessionRequired', checked=self.data.get('backchannelLogoutSessionRequired'),style='green'), - self.myparent.getTitledCheckBox("Front channel logout session required", name='frontChannelLogoutSessionRequired', checked=self.data.get('frontChannelLogoutSessionRequired'),style='green'), - - ],width=D() - ) - - self.tabs['SoftwareInfo'] = HSplit([ - self.myparent.getTitledText(title ="Client URI", name='clientUri', value=self.data.get('clientUri',''),style='green'), - self.myparent.getTitledText(title ="Policy URI", name='policyUri', value=self.data.get('policyUri',''),style='green'), - self.myparent.getTitledText(title ="Logo URI", name='logoUri', value=self.data.get('logoUri',''),style='green'), - self.myparent.getTitledText(title ="Term of service URI", name='tosUri', value=self.data.get('tosUri',''),style='green'), - self.myparent.getTitledText(title ="Contacts", name='contacts', value=self.data.get('contacts',''),style='green'), - VSplit([ - Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3,) - ]), - self.myparent.getTitledText(title ="Authorized JS origins", name='authorizedOrigins', value=self.data.get('authorizedOrigins',''),style='green'), - VSplit([ - Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3) - ]), - self.myparent.getTitledText(title ="Software id", name='softwareId', value=self.data.get('softwareId',''),style='green'), - self.myparent.getTitledText(title ="Software version", name='softwareVersion', value=self.data.get('softwareVersion',''), style='green'), - self.myparent.getTitledText(title ="Software statement", name='softwareStatement', value=self.data.get('softwareStatement',''), style='green'), + self.myparent.getTitledText( + title="Access token additional audiences", + name='additionalAudience', + value=self.data.get('additionalAudience',''), + style='green'), + VSplit([ + Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3) + ]), + self.myparent.getTitledText("Access token lifetime", name='accessTokenLifetime', value=self.data.get('accessTokenLifetime',''),style='green'), + self.myparent.getTitledText("Refresh token lifetime", name='refreshTokenLifetime', value=self.data.get('refreshTokenLifetime',''),style='green'), + self.myparent.getTitledText("Defult max authn age", name='defaultMaxAge', value=self.data.get('defaultMaxAge',''),style='green'), + + ],width=D()) - ],width=D()) + self.tabs['Logout'] = HSplit([ + + self.myparent.getTitledText("Front channel logout URI", name='frontChannelLogoutUri', value=self.data.get('frontChannelLogoutUri',''),style='green'), + self.myparent.getTitledText("Post logout redirect URI", name='postLogoutRedirectUris', value=self.data.get('postLogoutRedirectUris',''),style='green'), + self.myparent.getTitledText("Back channel logout URI", name='backchannelLogoutUri', value=self.data.get('backchannelLogoutUri',''),style='green'), + self.myparent.getTitledCheckBox("Back channel logout session required", name='backchannelLogoutSessionRequired', checked=self.data.get('backchannelLogoutSessionRequired'),style='green'), + self.myparent.getTitledCheckBox("Front channel logout session required", name='frontChannelLogoutSessionRequired', checked=self.data.get('frontChannelLogoutSessionRequired'),style='green'), - self.tabs['CIBA/PAR/UMA'] = HSplit([ - Label(text="CIBA",style='bold'), - self.myparent.getTitledRadioButton("Token delivery method", name='backchannelTokenDeliveryMode', current_value=self.data.get('backchannelTokenDeliveryMode'), values=['poll','push', 'ping'],style='green'), - self.myparent.getTitledText(title ="Client notification endpoint", name='backchannelClientNotificationEndpoint', value=self.data.get('backchannelClientNotificationEndpoint',''),style='green'), - self.myparent.getTitledCheckBox("Require user code param", name='backchannelUserCodeParameterSupported', checked=self.data.get('backchannelUserCodeParameterSupported'),style='green'), - - Label(text="PAR",style='bold'), - - self.myparent.getTitledText(title ="Request lifetime", name='parLifetime', value=self.data.get('parLifetime',''),style='green'), - self.myparent.getTitledCheckBox("Request PAR", name='sessionIdRequestParameterEnabled', checked=self.data.get('sessionIdRequestParameterEnabled'),style='green'), + ],width=D() + ) + """ + self.tabs['SoftwareInfo'] = HSplit([ + self.myparent.getTitledText(title ="Client URI", name='clientUri', value=self.data.get('clientUri',''),style='green'), + self.myparent.getTitledText(title ="Policy URI", name='policyUri', value=self.data.get('policyUri',''),style='green'), + self.myparent.getTitledText(title ="Logo URI", name='logoUri', value=self.data.get('logoUri',''),style='green'), + self.myparent.getTitledText(title ="Term of service URI", name='tosUri', value=self.data.get('tosUri',''),style='green'), + self.myparent.getTitledText(title ="Contacts", name='contacts', value=self.data.get('contacts',''),style='green'), + VSplit([ + Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3,) + ]), + self.myparent.getTitledText(title ="Authorized JS origins", name='authorizedOrigins', value=self.data.get('authorizedOrigins',''),style='green'), + VSplit([ + Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3) + ]), + self.myparent.getTitledText(title ="Software id", name='softwareId', value=self.data.get('softwareId',''),style='green'), + self.myparent.getTitledText(title ="Software version", name='softwareVersion', value=self.data.get('softwareVersion',''), style='green'), + self.myparent.getTitledText(title ="Software statement", name='softwareStatement', value=self.data.get('softwareStatement',''), style='green'), + + ],width=D()) + + self.tabs['CIBA/PAR/UMA'] = HSplit([ + Label(text="CIBA",style='bold'), + self.myparent.getTitledRadioButton("Token delivery method", name='backchannelTokenDeliveryMode', current_value=self.data.get('backchannelTokenDeliveryMode'), values=['poll','push', 'ping'],style='green'), + self.myparent.getTitledText(title ="Client notification endpoint", name='backchannelClientNotificationEndpoint', value=self.data.get('backchannelClientNotificationEndpoint',''),style='green'), + self.myparent.getTitledCheckBox("Require user code param", name='backchannelUserCodeParameterSupported', checked=self.data.get('backchannelUserCodeParameterSupported'),style='green'), + + Label(text="PAR",style='bold'), + + self.myparent.getTitledText(title ="Request lifetime", name='parLifetime', value=self.data.get('parLifetime',''),style='green'), + self.myparent.getTitledCheckBox("Request PAR", name='sessionIdRequestParameterEnabled', checked=self.data.get('sessionIdRequestParameterEnabled'),style='green'), + + Label("UMA",style='bold'), + self.myparent.getTitledRadioButton("PRT token type", name='applicationType!', values=['JWT', 'Reference'], current_value=self.data.get('applicationType!'),style='green'), + self.myparent.getTitledText(title ="Claims redirect URI", name='claimRedirectUris', value=self.data.get('claimRedirectUris',''),style='green'), - Label("UMA",style='bold'), - self.myparent.getTitledRadioButton("PRT token type", name='applicationType!', values=['JWT', 'Reference'], current_value=self.data.get('applicationType!'),style='green'), - self.myparent.getTitledText(title ="Claims redirect URI", name='claimRedirectUris', value=self.data.get('claimRedirectUris',''),style='green'), - - self.myparent.getTitledWidget( - "RPT Mofification Script", - name='rptClaimsScripts', - widget=DropDownWidget( - values=[('rptClaimsScripts','rptClaimsScripts')]), - ), - self.myparent.getTitledWidget( - title="title here", - name='claimRedirectUris', - widget=DropDownWidget( - values=[('claimRedirectUris','claimRedirectUris')]), - ), - Label(text="tabel",style='blue'), ## TODO with Jans VerticalNav - - ] - ) - - self.tabs['Encryption/Signing'] = HSplit([ - self.myparent.getTitledText(title ="Client JWKS URI", name='jwksUri', value=self.data.get('jwksUri',''),style='green'), - self.myparent.getTitledText(title ="Client JWKS", name='jwks', value=self.data.get('jwks',''),style='green'), - VSplit([ - Label(text="id_token"), - Label(text="a, b, c",style='red'), - ]), - VSplit([ - Label(text="Access token"), - Label(text="a",style='red'), - ]), - VSplit([ - Label(text="Userinfo"), - Label(text="a, b, c",style='red'), - ]), - VSplit([ - Label(text="JARM"), - Label(text="a, b, c",style='red'), - ]), - VSplit([ - Label(text="Request Object"), - Label(text="a, b, c",style='red'), - ]), + self.myparent.getTitledWidget( + "RPT Mofification Script", + name='rptClaimsScripts', + widget=DropDownWidget( + values=[('rptClaimsScripts','rptClaimsScripts')]), + ), + self.myparent.getTitledWidget( + title="title here", + name='claimRedirectUris', + widget=DropDownWidget( + values=[('claimRedirectUris','claimRedirectUris')]), + ), + Label(text="tabel",style='blue'), ## TODO with Jans VerticalNav - ] - ) - - self.tabs['Advanced Client Properties'] = HSplit([ - - self.myparent.getTitledCheckBox("Default Prompt login", name='defaultPromptLogin', checked=self.data.get('defaultPromptLogin'),style='green'), - VSplit([ - self.myparent.getTitledCheckBox("Persist Authorizations", name='persistClientAuthorizations', checked=self.data.get('persistClientAuthorizations'),style='green'), - # self.myparent.getTitledCheckBox("Keep expired", name='Supress', checked=self.data.get('Supress'),style='green'), - ]) , - self.myparent.getTitledCheckBox("Allow spontaneos scopes", name='allowSpontaneousScopes', checked=self.data.get('allowSpontaneousScopes'),style='green'), - - self.myparent.getTitledText("spontaneos scopes validation regex", name='spontaneousScopes', value=self.data.get('spontaneousScopes',''),style='green'), - VSplit([ - Label(text="Spontaneous Scopes",style='green'), - Button("view current", handler=self.myparent.show_again,left_symbol='',right_symbol='',) + ] + ) + + self.tabs['Encryption/Signing'] = HSplit([ + self.myparent.getTitledText(title ="Client JWKS URI", name='jwksUri', value=self.data.get('jwksUri',''),style='green'), + self.myparent.getTitledText(title ="Client JWKS", name='jwks', value=self.data.get('jwks',''),style='green'), + VSplit([ + Label(text="id_token"), + Label(text="a, b, c",style='red'), + ]), + VSplit([ + Label(text="Access token"), + Label(text="a",style='red'), + ]), + VSplit([ + Label(text="Userinfo"), + Label(text="a, b, c",style='red'), + ]), + VSplit([ + Label(text="JARM"), + Label(text="a, b, c",style='red'), + ]), + VSplit([ + Label(text="Request Object"), + Label(text="a, b, c",style='red'), + ]), + + ] + ) + + self.tabs['Advanced Client Properties'] = HSplit([ + + self.myparent.getTitledCheckBox("Default Prompt login", name='defaultPromptLogin', checked=self.data.get('defaultPromptLogin'),style='green'), + VSplit([ + self.myparent.getTitledCheckBox("Persist Authorizations", name='persistClientAuthorizations', checked=self.data.get('persistClientAuthorizations'),style='green'), + # self.myparent.getTitledCheckBox("Keep expired", name='Supress', checked=self.data.get('Supress'),style='green'), + ]) , + self.myparent.getTitledCheckBox("Allow spontaneos scopes", name='allowSpontaneousScopes', checked=self.data.get('allowSpontaneousScopes'),style='green'), + + self.myparent.getTitledText("spontaneos scopes validation regex", name='spontaneousScopes', value=self.data.get('spontaneousScopes',''),style='green'), + VSplit([ + Label(text="Spontaneous Scopes",style='green'), + Button("view current", handler=self.myparent.show_again,left_symbol='',right_symbol='',) + + ]) , + self.myparent.getTitledText("Initial Login URI", name='initiateLoginUri', value=self.data.get('initiateLoginUri',''),style='green'), + + VSplit([ + self.myparent.getTitledText("Request URIs", name='requestUris', value=self.data.get('requestUris',''),style='green'), + Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3,) + ]) , + self.myparent.getTitledWidget( + title="Defult ACR", + name='authorizedAcrValues', + widget=DropDownWidget( + values=[('authorizedAcrValues','authorizedAcrValues')]), + ), + + + VSplit([ + # self.myparent.getTitledText("Allowed ACRs", name='clientSecret', value=self.data.get('clientSecret',''),style='green'), + Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3,) + ]) , + self.myparent.getTitledText("TLS Subject DN", name='x5c', value=self.data.get('x5c',''),style='green'), + + VSplit([ + # self.myparent.getTitledCheckBox("Client Experiation Date", name='id_token_claims', checked=self.data.get('id_token_claims'),style='green'), + # attached to date + VSplit([Label(text="Client Experiation Date", + width=len("Client Experiation Date"),), + DateSelectWidget(data="20/2/1997")]), - ]) , - self.myparent.getTitledText("Initial Login URI", name='initiateLoginUri', value=self.data.get('initiateLoginUri',''),style='green'), - - VSplit([ - self.myparent.getTitledText("Request URIs", name='requestUris', value=self.data.get('requestUris',''),style='green'), - Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3,) - ]) , - self.myparent.getTitledWidget( - title="Defult ACR", - name='authorizedAcrValues', - widget=DropDownWidget( - values=[('authorizedAcrValues','authorizedAcrValues')]), - ), - VSplit([ - # self.myparent.getTitledText("Allowed ACRs", name='clientSecret', value=self.data.get('clientSecret',''),style='green'), - Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3,) - ]) , - self.myparent.getTitledText("TLS Subject DN", name='x5c', value=self.data.get('x5c',''),style='green'), - - VSplit([ - # self.myparent.getTitledCheckBox("Client Experiation Date", name='id_token_claims', checked=self.data.get('id_token_claims'),style='green'), - # attached to date - VSplit([Label(text="Client Experiation Date", - width=len("Client Experiation Date"),), - DateSelectWidget(data="20/2/1997")]), - - - - ]) , - - ],width=D() - ) - - self.tabs['Client Scripts'] = HSplit([ - - self.myparent.getTitledWidget( - title="Spontaneous Scopes", - name='authorizedAcrValues', - widget=DropDownWidget(values=[('spontaneousScopes','spontaneousScopes')]), - ), - self.myparent.getTitledWidget( - title="Update Token", - name='updateTokenScriptDns', - widget=DropDownWidget(values=[('updateTokenScriptDns','updateTokenScriptDns')]), - ), - self.myparent.getTitledWidget( - title="Post Authn", - name='postAuthnScripts', - widget=DropDownWidget(values=[('postAuthnScripts','postAuthnScripts')]), - ), - self.myparent.getTitledWidget( - title="Introspection", - name='introspectionScripts', - widget=DropDownWidget(values=[('introspectionScripts','introspectionScripts')]), - ), - self.myparent.getTitledWidget( - title="Password Grant", - name='dynamicRegistrationAllowedPasswordGrantScopes', - widget=DropDownWidget(values=[('dynamicRegistrationAllowedPasswordGrantScopes','dynamicRegistrationAllowedPasswordGrantScopes')]), - ), - self.myparent.getTitledWidget( - title="OAuth Consent", - name='consentGatheringScripts', - widget=DropDownWidget(values=[('consentGatheringScripts','consentGatheringScripts')]), - ), - - ] - ) - """ - self.left_nav = list(self.tabs.keys())[0] + ]) , + + ],width=D() + ) + + self.tabs['Client Scripts'] = HSplit([ + + self.myparent.getTitledWidget( + title="Spontaneous Scopes", + name='authorizedAcrValues', + widget=DropDownWidget(values=[('spontaneousScopes','spontaneousScopes')]), + ), + self.myparent.getTitledWidget( + title="Update Token", + name='updateTokenScriptDns', + widget=DropDownWidget(values=[('updateTokenScriptDns','updateTokenScriptDns')]), + ), + self.myparent.getTitledWidget( + title="Post Authn", + name='postAuthnScripts', + widget=DropDownWidget(values=[('postAuthnScripts','postAuthnScripts')]), + ), + self.myparent.getTitledWidget( + title="Introspection", + name='introspectionScripts', + widget=DropDownWidget(values=[('introspectionScripts','introspectionScripts')]), + ), + self.myparent.getTitledWidget( + title="Password Grant", + name='dynamicRegistrationAllowedPasswordGrantScopes', + widget=DropDownWidget(values=[('dynamicRegistrationAllowedPasswordGrantScopes','dynamicRegistrationAllowedPasswordGrantScopes')]), + ), + self.myparent.getTitledWidget( + title="OAuth Consent", + name='consentGatheringScripts', + widget=DropDownWidget(values=[('consentGatheringScripts','consentGatheringScripts')]), + ), + + ] + ) + """ + self.left_nav = list(self.tabs.keys())[0] def client_dialog_nav_selection_changed(self, selection): @@ -346,3 +366,5 @@ def __pt_container__(self): + + From 42005f31793082c1e542bf1674c522549645ad76 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Wed, 24 Aug 2022 02:42:14 -0700 Subject: [PATCH 060/364] fix jans-cli merging --- jans-cli-tui/jans-cli-tui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index 8a58ad93969..a07df8c50ad 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -512,7 +512,7 @@ def show_message(self, title, message, buttons=[]): app = get_app() focused_before = app.layout.current_window float_ = Float(content=dialog) - self.root_layout.floats.insert(0, float_) + self.root_layout.floats.append(float_) dialog.me = float_ dialog.focus_on_exit = focused_before app.layout.focus(dialog) From ed782e64ce6624c635d807dc5781705c93586ed6 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Wed, 24 Aug 2022 17:04:25 +0300 Subject: [PATCH 061/364] fix: jans-cli saving dialog data --- jans-cli-tui/jans-cli-tui.py | 25 +++++++++++++++++-------- jans-cli-tui/models/oauth/oauth.py | 2 +- jans-cli-tui/utils.py | 25 +++++++++++++------------ 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index a07df8c50ad..75189b79370 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -355,10 +355,13 @@ def getTitledText(self, title, name, value='', height=1, jans_help='', accept_ha ) ta.window.jans_name = name ta.window.jans_help = jans_help - ta.window.me = ta + li, cd, width = self.handle_long_string(title,[1]*num_lines,ta) - return VSplit([Label(text=li, width=width,style=style), cd], padding=1) + v = VSplit([Label(text=li, width=width,style=style), cd], padding=1) + v.me = ta + + return v def getTitledCheckBoxList(self, title, name, values, current_values=[], jans_help='', style=''): title += ': ' @@ -368,10 +371,12 @@ def getTitledCheckBoxList(self, title, name, values, current_values=[], jans_hel cbl.current_values = current_values cbl.window.jans_name = name cbl.window.jans_help = jans_help - cbl.window.me = cbl li, cd, width = self.handle_long_string(title, values, cbl) - return VSplit([Label(text=li, width=width, style=style, wrap_lines=False), cd]) + v = VSplit([Label(text=li, width=width, style=style, wrap_lines=False), cd]) + v.me = cbl + + return v def getTitledCheckBox(self, title, name, text='', checked=False, jans_help='', style=''): @@ -380,10 +385,12 @@ def getTitledCheckBox(self, title, name, text='', checked=False, jans_help='', s cb.checked = checked cb.window.jans_name = name cb.window.jans_help = jans_help - cb.window.me = cb li, cd, width = self.handle_long_string(title, text, cb) - return VSplit([Label(text=li, width=width, style=style, wrap_lines=False), cd]) + v = VSplit([Label(text=li, width=width, style=style, wrap_lines=False), cd]) + v.me = cb + + return v def getTitledRadioButton(self, title, name, values, current_value=None, jans_help='', style=''): title += ': ' @@ -401,12 +408,14 @@ def getTitledRadioButton(self, title, name, values, current_value=None, jans_hel def getTitledWidget(self, title, name, widget, jans_help='', style=''): - widget.window.me = widget widget.window.jans_name = name widget.window.jans_help = jans_help li, w2, width = self.handle_long_string(title, widget.values, widget) - return VSplit([Label(text=li, width=width, style=style), widget]) + v = VSplit([Label(text=li, width=width, style=style), widget]) + v.me = widget + + return v # ----------------------------------------------------------------- # def getButton(self, text, name, jans_help, handler=None): diff --git a/jans-cli-tui/models/oauth/oauth.py b/jans-cli-tui/models/oauth/oauth.py index 6a86e4e7f29..2938c67e5a6 100644 --- a/jans-cli-tui/models/oauth/oauth.py +++ b/jans-cli-tui/models/oauth/oauth.py @@ -238,7 +238,7 @@ def edit_client_dialog(self, **params): def save_client(self, dialog): self.logger.debug(dialog.data) - + return response = self.cli_object.process_command_by_id( operation_id='put-oauth-openid-clients' if dialog.data.get('inum') else 'post-oauth-openid-clients', url_suffix='', diff --git a/jans-cli-tui/utils.py b/jans-cli-tui/utils.py index 46170d34c76..e9eb512a80a 100644 --- a/jans-cli-tui/utils.py +++ b/jans-cli-tui/utils.py @@ -13,18 +13,19 @@ def make_data_from_dialog(self): data = {} for tab in self.tabs: for item in self.tabs[tab].children: - if hasattr(item, 'children') and len(item.children)>1 and hasattr(item.children[1], 'jans_name'): - key_ = item.children[1].jans_name - if isinstance(item.children[1].me, prompt_toolkit.widgets.base.TextArea): - value_ = item.children[1].me.text - elif isinstance(item.children[1].me, prompt_toolkit.widgets.base.CheckboxList): - value_ = item.children[1].me.current_values - elif isinstance(item.children[1].me, prompt_toolkit.widgets.base.RadioList): - value_ = item.children[1].me.current_value - elif isinstance(item.children[1].me, prompt_toolkit.widgets.base.Checkbox): - value_ = item.children[1].me.checked - elif isinstance(item.children[1].me, DropDownWidget): - value_ = item.children[1].me.value + if hasattr(item, 'me'): + me = item.me + key_ = me.window.jans_name + if isinstance(me, prompt_toolkit.widgets.base.TextArea): + value_ = me.text + elif isinstance(me, prompt_toolkit.widgets.base.CheckboxList): + value_ = me.current_values + elif isinstance(me, prompt_toolkit.widgets.base.RadioList): + value_ = me.current_value + elif isinstance(me, prompt_toolkit.widgets.base.Checkbox): + value_ = me.checked + elif isinstance(me, DropDownWidget): + value_ = me.value data[key_] = value_ return data From d17c83bc6b4f611670414bdd1d7cd93c35708413 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Wed, 24 Aug 2022 18:18:56 +0300 Subject: [PATCH 062/364] fix: jans-cli save client dialog --- jans-cli-tui/jans-cli-tui.py | 33 ++++++++++--------- .../models/oauth/edit_client_dialog.py | 8 ++--- jans-cli-tui/models/oauth/oauth.py | 2 +- jans-cli-tui/utils.py | 4 +-- 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index 75189b79370..afa9d4e9d17 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -287,7 +287,9 @@ def get_help_from_schema(self, schema, jans_name): if prop == jans_name: return schema['properties'][jans_name].get('description', '') - def handle_long_string (self,text,values,cb): + def handle_long_string (self, text, values, widget): + return text, values, widget + #Abdulwehab NEVER do any change for widget. I renamed cb as widget lines = [] if len(text) > 20 : title_list=text.split(' ') @@ -315,12 +317,12 @@ def handle_long_string (self,text,values,cb): if title_lines <= len(values) : ### if num of values (value lines) < = title_lines - lines_under_value = 0 + lines_under_value = 0 else : lines_under_value = abs(title_lines-len(values)) if lines_under_value !=0 : - cd = HSplit([ + cd = HSplit([ cb, Label(text=('\n')*(lines_under_value-1)) ]) @@ -343,11 +345,10 @@ def handle_long_string (self,text,values,cb): def getTitledText(self, title, name, value='', height=1, jans_help='', accept_handler=None, read_only=False, width=None, style=''): title += ': ' - num_lines = min(len(str(value).split("\n")),height) - multiline = height > 1 ta = TextArea( text=str(value), - multiline=multiline, + multiline=height > 1, + height=height, read_only=read_only, style="class:textarea-readonly" if read_only else "class:textarea", focusable=not read_only, @@ -356,9 +357,9 @@ def getTitledText(self, title, name, value='', height=1, jans_help='', accept_ha ta.window.jans_name = name ta.window.jans_help = jans_help - li, cd, width = self.handle_long_string(title,[1]*num_lines,ta) + #li, cd, width = self.handle_long_string(title,[1]*num_lines,ta) - v = VSplit([Label(text=li, width=width,style=style), cd], padding=1) + v = VSplit([Label(text=title, width=len(title), style=style), ta], padding=1) v.me = ta return v @@ -371,9 +372,9 @@ def getTitledCheckBoxList(self, title, name, values, current_values=[], jans_hel cbl.current_values = current_values cbl.window.jans_name = name cbl.window.jans_help = jans_help - li, cd, width = self.handle_long_string(title, values, cbl) + #li, cd, width = self.handle_long_string(title, values, cbl) - v = VSplit([Label(text=li, width=width, style=style, wrap_lines=False), cd]) + v = VSplit([Label(text=title, width=len(title), style=style, wrap_lines=False), cbl]) v.me = cbl return v @@ -385,9 +386,9 @@ def getTitledCheckBox(self, title, name, text='', checked=False, jans_help='', s cb.checked = checked cb.window.jans_name = name cb.window.jans_help = jans_help - li, cd, width = self.handle_long_string(title, text, cb) + #li, cd, width = self.handle_long_string(title, text, cb) - v = VSplit([Label(text=li, width=width, style=style, wrap_lines=False), cd]) + v = VSplit([Label(text=title, width=len(title), style=style, wrap_lines=False), cb]) v.me = cb return v @@ -402,17 +403,17 @@ def getTitledRadioButton(self, title, name, values, current_value=None, jans_hel rl.window.jans_name = name rl.window.jans_help = jans_help rl.window.me = rl - li, rl2, width = self.handle_long_string(title, values, rl) + #li, rl2, width = self.handle_long_string(title, values, rl) - return VSplit([Label(text=li, width=width, style=style), rl2],) + return VSplit([Label(text=title, width=len(title), style=style), rl]) def getTitledWidget(self, title, name, widget, jans_help='', style=''): widget.window.jans_name = name widget.window.jans_help = jans_help - li, w2, width = self.handle_long_string(title, widget.values, widget) + #li, w2, width = self.handle_long_string(title, widget.values, widget) - v = VSplit([Label(text=li, width=width, style=style), widget]) + v = VSplit([Label(text=title, width=len(title), style=style), widget]) v.me = widget return v diff --git a/jans-cli-tui/models/oauth/edit_client_dialog.py b/jans-cli-tui/models/oauth/edit_client_dialog.py index 860a0dea768..d351652f04a 100644 --- a/jans-cli-tui/models/oauth/edit_client_dialog.py +++ b/jans-cli-tui/models/oauth/edit_client_dialog.py @@ -32,8 +32,8 @@ def __init__(self, parent, title, data, buttons=[], save_handler=None): def save(): self.data = self.make_data_from_dialog() - - for list_key in ('redirectUris', 'scopes'): + self.data['disabled'] = not self.data['disabled'] + for list_key in ('redirectUris', 'scopes', 'postLogoutRedirectUris'): if self.data[list_key]: self.data[list_key] = self.data[list_key].splitlines() @@ -182,8 +182,8 @@ def prepare_tabs(self): self.tabs['Logout'] = HSplit([ - self.myparent.getTitledText("Front channel logout URI", name='frontChannelLogoutUri', value=self.data.get('frontChannelLogoutUri',''),style='green'), - self.myparent.getTitledText("Post logout redirect URI", name='postLogoutRedirectUris', value=self.data.get('postLogoutRedirectUris',''),style='green'), + self.myparent.getTitledText("Front channel logout URI", name='frontChannelLogoutUri', value=self.data.get('frontChannelLogoutUri',''), style='green'), + self.myparent.getTitledText("Post logout redirect URIs", name='postLogoutRedirectUris', value='\n'.join(self.data.get('postLogoutRedirectUris',[])), height=3, style='green'), self.myparent.getTitledText("Back channel logout URI", name='backchannelLogoutUri', value=self.data.get('backchannelLogoutUri',''),style='green'), self.myparent.getTitledCheckBox("Back channel logout session required", name='backchannelLogoutSessionRequired', checked=self.data.get('backchannelLogoutSessionRequired'),style='green'), self.myparent.getTitledCheckBox("Front channel logout session required", name='frontChannelLogoutSessionRequired', checked=self.data.get('frontChannelLogoutSessionRequired'),style='green'), diff --git a/jans-cli-tui/models/oauth/oauth.py b/jans-cli-tui/models/oauth/oauth.py index 2938c67e5a6..6a86e4e7f29 100644 --- a/jans-cli-tui/models/oauth/oauth.py +++ b/jans-cli-tui/models/oauth/oauth.py @@ -238,7 +238,7 @@ def edit_client_dialog(self, **params): def save_client(self, dialog): self.logger.debug(dialog.data) - return + response = self.cli_object.process_command_by_id( operation_id='put-oauth-openid-clients' if dialog.data.get('inum') else 'post-oauth-openid-clients', url_suffix='', diff --git a/jans-cli-tui/utils.py b/jans-cli-tui/utils.py index e9eb512a80a..8130949ab92 100644 --- a/jans-cli-tui/utils.py +++ b/jans-cli-tui/utils.py @@ -18,12 +18,12 @@ def make_data_from_dialog(self): key_ = me.window.jans_name if isinstance(me, prompt_toolkit.widgets.base.TextArea): value_ = me.text + elif isinstance(me, prompt_toolkit.widgets.base.Checkbox): + value_ = me.checked elif isinstance(me, prompt_toolkit.widgets.base.CheckboxList): value_ = me.current_values elif isinstance(me, prompt_toolkit.widgets.base.RadioList): value_ = me.current_value - elif isinstance(me, prompt_toolkit.widgets.base.Checkbox): - value_ = me.checked elif isinstance(me, DropDownWidget): value_ = me.value data[key_] = value_ From 272fbabe3facef672d042ff0de96c88b99dcb04c Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Wed, 24 Aug 2022 20:33:57 +0300 Subject: [PATCH 063/364] fix: jans-cli replace dashes with VerticalLine --- jans-cli-tui/wui_components/jans_dialog_with_nav.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/jans-cli-tui/wui_components/jans_dialog_with_nav.py b/jans-cli-tui/wui_components/jans_dialog_with_nav.py index 5ac25c58b61..515ce0884af 100644 --- a/jans-cli-tui/wui_components/jans_dialog_with_nav.py +++ b/jans-cli-tui/wui_components/jans_dialog_with_nav.py @@ -9,6 +9,7 @@ from prompt_toolkit.widgets import ( Button, Dialog, + VerticalLine, ) @@ -38,9 +39,9 @@ def create_window(self): HSplit([ self.navbar ], width= (max_data_str )), - Window(width=1, char="|",), + VerticalLine(), ScrollablePane(content=self.content, height=height), - ], width=120), + ], width=120, padding=1), buttons=[ Button( From 6200e30bb71f93a1fec3a864538f28a450f7f324 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Thu, 25 Aug 2022 12:06:14 +0300 Subject: [PATCH 064/364] fix: jans-cli code clean --- jans-cli-tui/jans-cli-tui.py | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index afa9d4e9d17..89ce81eabcf 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -80,13 +80,10 @@ def __init__(self): self.app = get_app() self.show_dialog = False ## ## ## ## self.set_keybindings() - self.containers = {} # -------------------------------------------------------------------------------- # - self.dialogs = {} - self.tabs = {} - self.active_dialog_select = '' - self.Auth_clients_tabs = {} + self.dialog_width = int(self.width*0.9) # 120 ## to be dynamic + self.dialog_height = int(self.height*0.8) ## to be dynamic self.keyboard = Controller() @@ -96,7 +93,6 @@ def __init__(self): FormattedTextControl(self.update_status_bar), style="class:status", height=1 ) - self.prapare_dialogs() JansAuthServer.initialize(self) @@ -253,16 +249,6 @@ async def coroutine(): ensure_future(coroutine()) - def dialog_back_but(self): ## BACK - self.active_dialog_select = '' - self.show_dialog = False - self.layout.focus(self.center_frame) - - def prapare_dialogs(self): - self.data_show_client_dialog = Label(text='Selected Line Data as Json') - self.dialog_width = int(self.width*0.9) # 120 ## to be dynamic - self.dialog_height = int(self.height*0.8) ## to be dynamic - def focus_next(self, ev): focus_next(ev) From a503c8f896d2ebfedb20cb597d4678f3558693cb Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Thu, 25 Aug 2022 13:38:55 +0300 Subject: [PATCH 065/364] fix: jans-cli show-message dialog buttons --- .../wui_components/jans_message_dialog.py | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/jans-cli-tui/wui_components/jans_message_dialog.py b/jans-cli-tui/wui_components/jans_message_dialog.py index a5a832b21b4..10230d69593 100644 --- a/jans-cli-tui/wui_components/jans_message_dialog.py +++ b/jans-cli-tui/wui_components/jans_message_dialog.py @@ -13,18 +13,26 @@ def __init__(self, title, body, buttons=[], focus_on_exit=None): if not buttons: buttons = ["OK"] - def exit_me(result): + def exit_me(result, handler): + if handler: + handler() self.result = result app = get_app() - app.layout.focus(self.focus_on_exit) + if self.me in app.root_layout.floats: app.root_layout.floats.remove(self.me) + try: + app.layout.focus(self.focus_on_exit) + except: + pass + blist = [] - for b in buttons: - handler = partial(exit_me, b) - button = Button(text=b, handler=handler) + for button in buttons: + if isinstance(button, str): + button = Button(text=button) + button.handler = partial(exit_me, button.text, button.handler) blist.append(button) self.dialog = Dialog( @@ -35,5 +43,8 @@ def exit_me(result): modal=True, ) + app = get_app() + app.layout.focus(self.dialog) + def __pt_container__(self): return self.dialog From 2fedd6f466fdf88998f14c36cfc10e3fc1a204e6 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Fri, 26 Aug 2022 12:10:59 -0700 Subject: [PATCH 066/364] fix jans-cli fix datePicker 0.2 --- .../models/oauth/edit_client_dialog.py | 130 ++++++++++-------- jans-cli-tui/models/oauth/oauth.py | 4 +- jans-cli-tui/utils.py | 6 + .../wui_components/jans_data_picker.py | 69 ++++++++-- 4 files changed, 136 insertions(+), 73 deletions(-) diff --git a/jans-cli-tui/models/oauth/edit_client_dialog.py b/jans-cli-tui/models/oauth/edit_client_dialog.py index d351652f04a..29c9375a0c3 100644 --- a/jans-cli-tui/models/oauth/edit_client_dialog.py +++ b/jans-cli-tui/models/oauth/edit_client_dialog.py @@ -72,9 +72,11 @@ def cancel(): def prepare_tabs(self): - self.myparent.logger.debug("Data: "+str(self.data)) + schema = self.myparent.cli_object.get_schema_from_reference('#/components/schemas/Client') + # self.data['expirationDate'] = 1331856000000 ## to test widget + self.tabs = OrderedDict() self.tabs['Basic'] = HSplit([ @@ -190,7 +192,7 @@ def prepare_tabs(self): ],width=D() ) - """ + self.tabs['SoftwareInfo'] = HSplit([ self.myparent.getTitledText(title ="Client URI", name='clientUri', value=self.data.get('clientUri',''),style='green'), self.myparent.getTitledText(title ="Policy URI", name='policyUri', value=self.data.get('policyUri',''),style='green'), @@ -224,19 +226,21 @@ def prepare_tabs(self): Label("UMA",style='bold'), self.myparent.getTitledRadioButton("PRT token type", name='applicationType!', values=['JWT', 'Reference'], current_value=self.data.get('applicationType!'),style='green'), self.myparent.getTitledText(title ="Claims redirect URI", name='claimRedirectUris', value=self.data.get('claimRedirectUris',''),style='green'), - - self.myparent.getTitledWidget( - "RPT Mofification Script", - name='rptClaimsScripts', - widget=DropDownWidget( - values=[('rptClaimsScripts','rptClaimsScripts')]), - ), - self.myparent.getTitledWidget( - title="title here", - name='claimRedirectUris', - widget=DropDownWidget( - values=[('claimRedirectUris','claimRedirectUris')]), - ), + + + self.myparent.getTitledText("RPT Mofification Script", + name='rptClaimsScripts', + value='\n'.join(self.data.get('rptClaimsScripts', [])), + height=3, + style='green'), + + self.myparent.getTitledText("Claims Gathering Script", + name='claimRedirectUris', + value='\n'.join(self.data.get('claimRedirectUris', [])), + height=3, + style='green'), + + Label(text="tabel",style='blue'), ## TODO with Jans VerticalNav ] @@ -290,13 +294,12 @@ def prepare_tabs(self): self.myparent.getTitledText("Request URIs", name='requestUris', value=self.data.get('requestUris',''),style='green'), Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3,) ]) , - self.myparent.getTitledWidget( - title="Defult ACR", - name='authorizedAcrValues', - widget=DropDownWidget( - values=[('authorizedAcrValues','authorizedAcrValues')]), - ), + self.myparent.getTitledText("Default ACR", + name='authorizedAcrValues', + value='\n'.join(self.data.get('authorizedAcrValues', [])), + height=3, + style='green'), VSplit([ # self.myparent.getTitledText("Allowed ACRs", name='clientSecret', value=self.data.get('clientSecret',''),style='green'), @@ -308,52 +311,67 @@ def prepare_tabs(self): # self.myparent.getTitledCheckBox("Client Experiation Date", name='id_token_claims', checked=self.data.get('id_token_claims'),style='green'), # attached to date VSplit([Label(text="Client Experiation Date", - width=len("Client Experiation Date"),), - DateSelectWidget(data="20/2/1997")]), - - + width=len("Client Experiation Date"),), + # DateSelectWidget(data=self.data.get('expirationDate','')), + DateSelectWidget(data="2022-11-05T14:45:26"), + ] + ), ]) , ],width=D() ) + + self.tabs['Client Scripts'] = HSplit([ - self.myparent.getTitledWidget( - title="Spontaneous Scopes", - name='authorizedAcrValues', - widget=DropDownWidget(values=[('spontaneousScopes','spontaneousScopes')]), - ), - self.myparent.getTitledWidget( - title="Update Token", - name='updateTokenScriptDns', - widget=DropDownWidget(values=[('updateTokenScriptDns','updateTokenScriptDns')]), - ), - self.myparent.getTitledWidget( - title="Post Authn", - name='postAuthnScripts', - widget=DropDownWidget(values=[('postAuthnScripts','postAuthnScripts')]), - ), - self.myparent.getTitledWidget( - title="Introspection", - name='introspectionScripts', - widget=DropDownWidget(values=[('introspectionScripts','introspectionScripts')]), - ), - self.myparent.getTitledWidget( - title="Password Grant", - name='dynamicRegistrationAllowedPasswordGrantScopes', - widget=DropDownWidget(values=[('dynamicRegistrationAllowedPasswordGrantScopes','dynamicRegistrationAllowedPasswordGrantScopes')]), - ), - self.myparent.getTitledWidget( - title="OAuth Consent", - name='consentGatheringScripts', - widget=DropDownWidget(values=[('consentGatheringScripts','consentGatheringScripts')]), - ), + + self.myparent.getTitledText("Spontaneous Scopes", + name='spontaneousScopes', + value='\n'.join(self.data.get('spontaneousScopes', [])), + height=3, + style='green'), + + # --------------------------------------------------------------------------------------# + self.myparent.getTitledText("Update Token", + name='updateTokenScriptDns', + value='\n'.join(self.data.get('updateTokenScriptDns', [])), + height=3, + style='green'), + + # --------------------------------------------------------------------------------------# + self.myparent.getTitledText("Post Authn", + name='postAuthnScripts', + value='\n'.join(self.data.get('postAuthnScripts', [])), + height=3, + style='green'), + + # --------------------------------------------------------------------------------------# + self.myparent.getTitledText("Introspection", + name='introspectionScripts', + value='\n'.join(self.data.get('introspectionScripts', [])), + height=3, + style='green'), + + # --------------------------------------------------------------------------------------# + self.myparent.getTitledText("Password Grant", + name='dynamicRegistrationAllowedPasswordGrantScopes', + value='\n'.join(self.data.get('dynamicRegistrationAllowedPasswordGrantScopes', [])), + height=3, + style='green'), + + # --------------------------------------------------------------------------------------# + self.myparent.getTitledText("OAuth Consent", + name='consentGatheringScripts', + value='\n'.join(self.data.get('consentGatheringScripts', [])), + height=3, + style='green'), + ] ) - """ + self.left_nav = list(self.tabs.keys())[0] diff --git a/jans-cli-tui/models/oauth/oauth.py b/jans-cli-tui/models/oauth/oauth.py index 6a86e4e7f29..7e488a2014d 100644 --- a/jans-cli-tui/models/oauth/oauth.py +++ b/jans-cli-tui/models/oauth/oauth.py @@ -124,7 +124,7 @@ def oauth_update_clients(self, pattern=''): result = rsponse.json() except Exception: self.show_message("Error getting clients", str(rsponse.text)) - press_tab + #press_tab return @@ -237,7 +237,7 @@ def edit_client_dialog(self, **params): def save_client(self, dialog): - self.logger.debug(dialog.data) + # self.logger.debug(dialog.data) response = self.cli_object.process_command_by_id( operation_id='put-oauth-openid-clients' if dialog.data.get('inum') else 'post-oauth-openid-clients', diff --git a/jans-cli-tui/utils.py b/jans-cli-tui/utils.py index 8130949ab92..13e5f33861b 100644 --- a/jans-cli-tui/utils.py +++ b/jans-cli-tui/utils.py @@ -12,6 +12,8 @@ class DialogUtils: def make_data_from_dialog(self): data = {} for tab in self.tabs: + self.myparent.logger.debug("********** {} ***********".format(str(tab))) + for item in self.tabs[tab].children: if hasattr(item, 'me'): me = item.me @@ -26,6 +28,10 @@ def make_data_from_dialog(self): value_ = me.current_value elif isinstance(me, DropDownWidget): value_ = me.value + elif isinstance(me,DateSelectWidget): + self.myparent.logger.debug("********** DateSelectWidget ***********") + value_ = me.value + data[key_] = value_ return data diff --git a/jans-cli-tui/wui_components/jans_data_picker.py b/jans-cli-tui/wui_components/jans_data_picker.py index 49ac1dc2eb1..f3ec1f43584 100644 --- a/jans-cli-tui/wui_components/jans_data_picker.py +++ b/jans-cli-tui/wui_components/jans_data_picker.py @@ -22,17 +22,28 @@ ) import calendar - +import time +# ts = time.strptime(x[:19], "%Y-%m-%dT%H:%M:%S") +# time.strftime("%m/%d/%Y", ts) +# '11/27/2023' #### not finished yet >> data +import datetime class JansSelectDate: - def __init__(self, date=''): - self.date = date - self.months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] + def __init__(self, date='',months=[]): + if date != '': + self.date = date #"11/27/2023" + else: + today = datetime.date.today() + self.date = str(today.month) +'/' +str(today.day) +'/'+str(today.year) ## '11/27/2023' ## + + self.months = months + self.cord_y = 0 self.cord_x = 0 + self.extract_date(self.date) #self.depug=Label(text="entries = "+str(self.entries[self.cord_y][self.cord_x])+':',) @@ -65,10 +76,14 @@ def __init__(self, date=''): ), ]) - def extract_date(self,date): - ### this function is for the init date >> passed date from data - day = int(date.split('/')[0]) - self.current_month = int(date.split('/')[1]) + # def set_value(self, value): + # self.value = self.date + + def extract_date(self,date): #"11/27/2023" + ### this function is for the init date >> passed date from `data` + + day = int(date.split('/')[1]) + self.current_month = int(date.split('/')[0]) self.current_year = int(date.split('/')[-1]) month_week_list = calendar.monthcalendar(int(self.current_year),int(self.current_month)) @@ -121,14 +136,14 @@ def inc_month(self,day): if self.current_month != 12: self.current_month+=1 self.month_label = Label(text=self.months[self.current_month-1],width=len(self.months[self.current_month-1])) - current_date = str(day)+'/'+str(self.current_month) + '/'+str(self.current_year) + current_date = str(self.current_month)+'/'+str(day) + '/'+str(self.current_year) self.extract_date(current_date) def dec_month(self,day): if self.current_month > 1: self.current_month-=1 self.month_label = Label(text=self.months[self.current_month-1],width=len(self.months[self.current_month-1])) - current_date = str(day)+'/'+str(self.current_month) + '/'+str(self.current_year) + current_date = str(self.current_month)+'/'+str(day) + '/'+str(self.current_year) self.extract_date(current_date) def inc_year(self,day): @@ -179,13 +194,22 @@ def __pt_container__(self): return self.container -class DateSelectWidget: +class DateSelectWidget: # ex: data = "2023-11-27T14:05:35" def __init__(self,data): - self.date = data #"20/2/1997" + + self.months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] - self.text = "Enter to Select" + if data != '': + ts = time.strptime(data[:19], "%Y-%m-%dT%H:%M:%S") # "2023-11-27" + self.value = data + self.date = time.strftime("%m/%d/%Y", ts) # "11/27/2023" + self.text = self.date.split('/')[1] + '/' +self.months[int(self.date.split('/')[0])-1] + '/' +self.date.split('/')[2] + else: + self.date='' + self.text = "Enter to Select" + self.dropdown = True self.window = Window( content=FormattedTextControl( @@ -194,10 +218,25 @@ def __init__(self,data): key_bindings=self._get_key_bindings(), ), height= 10) #D()) #5 ## large sized enties get >> (window too small) - self.select_box = JansSelectDate(self.date) + self.select_box = JansSelectDate(date=self.date,months=self.months ) self.select_box_float = Float(content=self.select_box, xcursor=True, ycursor=True) + @property + def value(self): + # with open('./hopa2.log', 'a') as f: + # f.write("_value: "+str("2022-11-05T14:45:26")) + # do some conversion staff and set to date_string in format YYYY-MM-DDTHH:mm:ss + return "2022-11-05T14:45:26" + + @value.setter + def value(self, value): + # with open('./hopa.log', 'a') as f: + # f.write("_value: "+str(value)) + # do some conversion staff convert value to internal date format and set to _value + self._vallue = "2022-11-05T14:45:26" + + def _get_text(self): if get_app().layout.current_window is self.window: return HTML('> <'.format('#00FF00', self.text)) @@ -218,8 +257,8 @@ def _enter(event) -> None: get_app().layout.container.floats.append( self.select_box_float) else: self.text = str(self.select_box.entries[self.select_box.cord_y][self.select_box.cord_x] )+'/'+ str(self.select_box.months[self.select_box.current_month-1] ) +'/'+str(self.select_box.current_year) - get_app().layout.container.floats.remove(self.select_box_float) + self.value = str(self.select_box.entries[self.select_box.cord_y][self.select_box.cord_x] )+'/'+ str(self.select_box.current_month) +'/'+str(self.select_box.current_year) +"T23:59:59" @kb.add("up") def _up(event): From 69fec787b51b1d784d027dd894daee26e25ff4be Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Fri, 26 Aug 2022 22:58:10 +0300 Subject: [PATCH 067/364] fix: jans-cli titled dateselect widget --- .../models/oauth/edit_client_dialog.py | 24 ++++++++++--------- jans-cli-tui/models/oauth/oauth.py | 2 +- .../wui_components/jans_data_picker.py | 5 ++-- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/jans-cli-tui/models/oauth/edit_client_dialog.py b/jans-cli-tui/models/oauth/edit_client_dialog.py index 29c9375a0c3..66cdd4a24d5 100644 --- a/jans-cli-tui/models/oauth/edit_client_dialog.py +++ b/jans-cli-tui/models/oauth/edit_client_dialog.py @@ -307,17 +307,19 @@ def prepare_tabs(self): ]) , self.myparent.getTitledText("TLS Subject DN", name='x5c', value=self.data.get('x5c',''),style='green'), - VSplit([ - # self.myparent.getTitledCheckBox("Client Experiation Date", name='id_token_claims', checked=self.data.get('id_token_claims'),style='green'), - # attached to date - VSplit([Label(text="Client Experiation Date", - width=len("Client Experiation Date"),), - # DateSelectWidget(data=self.data.get('expirationDate','')), - DateSelectWidget(data="2022-11-05T14:45:26"), - ] - ), - - ]) , + + self.myparent.getTitledWidget( + "Client Experiation Date", + name='expirationDate', + widget=DateSelectWidget( + value=self.data.get('expirationDate', "2022-11-05T14:45:26") + ), + jans_help=self.myparent.get_help_from_schema(schema, 'expirationDate'), + style='green' + ), + + + ],width=D() ) diff --git a/jans-cli-tui/models/oauth/oauth.py b/jans-cli-tui/models/oauth/oauth.py index 7e488a2014d..8e7c05adf54 100644 --- a/jans-cli-tui/models/oauth/oauth.py +++ b/jans-cli-tui/models/oauth/oauth.py @@ -237,7 +237,7 @@ def edit_client_dialog(self, **params): def save_client(self, dialog): - # self.logger.debug(dialog.data) + self.logger.debug(dialog.data) response = self.cli_object.process_command_by_id( operation_id='put-oauth-openid-clients' if dialog.data.get('inum') else 'post-oauth-openid-clients', diff --git a/jans-cli-tui/wui_components/jans_data_picker.py b/jans-cli-tui/wui_components/jans_data_picker.py index f3ec1f43584..371f5e7062e 100644 --- a/jans-cli-tui/wui_components/jans_data_picker.py +++ b/jans-cli-tui/wui_components/jans_data_picker.py @@ -195,11 +195,10 @@ def __pt_container__(self): class DateSelectWidget: # ex: data = "2023-11-27T14:05:35" - def __init__(self,data): - + def __init__(self, value): self.months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] - if data != '': + if not value: ts = time.strptime(data[:19], "%Y-%m-%dT%H:%M:%S") # "2023-11-27" self.value = data self.date = time.strftime("%m/%d/%Y", ts) # "11/27/2023" From 69d4f51b03b947ae359824df8715e83d3a90b5b2 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Fri, 26 Aug 2022 23:21:56 +0300 Subject: [PATCH 068/364] fix: jans-cli comment some widgets --- jans-cli-tui/models/oauth/edit_client_dialog.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/jans-cli-tui/models/oauth/edit_client_dialog.py b/jans-cli-tui/models/oauth/edit_client_dialog.py index 66cdd4a24d5..2ac88057061 100644 --- a/jans-cli-tui/models/oauth/edit_client_dialog.py +++ b/jans-cli-tui/models/oauth/edit_client_dialog.py @@ -194,10 +194,10 @@ def prepare_tabs(self): ) self.tabs['SoftwareInfo'] = HSplit([ - self.myparent.getTitledText(title ="Client URI", name='clientUri', value=self.data.get('clientUri',''),style='green'), - self.myparent.getTitledText(title ="Policy URI", name='policyUri', value=self.data.get('policyUri',''),style='green'), - self.myparent.getTitledText(title ="Logo URI", name='logoUri', value=self.data.get('logoUri',''),style='green'), - self.myparent.getTitledText(title ="Term of service URI", name='tosUri', value=self.data.get('tosUri',''),style='green'), + #self.myparent.getTitledText(title ="Client URI", name='clientUri', value=self.data.get('clientUri',''),style='green'), + #self.myparent.getTitledText(title ="Policy URI", name='policyUri', value=self.data.get('policyUri',''),style='green'), + #self.myparent.getTitledText(title ="Logo URI", name='logoUri', value=self.data.get('logoUri',''),style='green'), + #self.myparent.getTitledText(title ="Term of service URI", name='tosUri', value=self.data.get('tosUri',''),style='green'), self.myparent.getTitledText(title ="Contacts", name='contacts', value=self.data.get('contacts',''),style='green'), VSplit([ Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3,) @@ -307,7 +307,6 @@ def prepare_tabs(self): ]) , self.myparent.getTitledText("TLS Subject DN", name='x5c', value=self.data.get('x5c',''),style='green'), - self.myparent.getTitledWidget( "Client Experiation Date", name='expirationDate', @@ -317,9 +316,7 @@ def prepare_tabs(self): jans_help=self.myparent.get_help_from_schema(schema, 'expirationDate'), style='green' ), - - - + ],width=D() ) From 5f5e92602fe00cb613978f425d163ff663a4edb8 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Fri, 26 Aug 2022 23:56:04 +0300 Subject: [PATCH 069/364] fix: jans-cli saving accessTokenAsJwt --- jans-cli-tui/jans-cli-tui.py | 6 ++++-- jans-cli-tui/models/oauth/edit_client_dialog.py | 17 +++++++---------- jans-cli-tui/utils.py | 1 + 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index 89ce81eabcf..fde510da130 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -388,10 +388,12 @@ def getTitledRadioButton(self, title, name, values, current_value=None, jans_hel rl.current_value = current_value rl.window.jans_name = name rl.window.jans_help = jans_help - rl.window.me = rl #li, rl2, width = self.handle_long_string(title, values, rl) - return VSplit([Label(text=title, width=len(title), style=style), rl]) + v = VSplit([Label(text=title, width=len(title), style=style), rl]) + v.me = rl + + return v def getTitledWidget(self, title, name, widget, jans_help='', style=''): diff --git a/jans-cli-tui/models/oauth/edit_client_dialog.py b/jans-cli-tui/models/oauth/edit_client_dialog.py index 2ac88057061..617ab29e9e7 100644 --- a/jans-cli-tui/models/oauth/edit_client_dialog.py +++ b/jans-cli-tui/models/oauth/edit_client_dialog.py @@ -37,6 +37,9 @@ def save(): if self.data[list_key]: self.data[list_key] = self.data[list_key].splitlines() + if 'accessTokenAsJwt' in self.data: + self.data['accessTokenAsJwt'] = self.data['accessTokenAsJwt'] == 'jwt' + cfr = self.check_required_fields() self.myparent.logger.debug("CFR: "+str(cfr)) if not cfr: @@ -139,18 +142,12 @@ def prepare_tabs(self): ) self.tabs['Tokens'] = HSplit([ - # self.myparent.getTitledRadioButton( - # "Access Token Type", - # name='accessTokenAsJwt', - # values=['JWT','Reference'], - # current_value=self.data.get('accessTokenAsJwt'), - # jans_help=self.myparent.get_help_from_schema(schema, 'accessTokenAsJwt'), - # style='green'), - - self.myparent.getTitledCheckBox( + self.myparent.getTitledRadioButton( "Access Token Type", name='accessTokenAsJwt', - checked=self.data.get('accessTokenAsJwt'), + values=[('jwt', 'JWT'), ('reference', 'Reference')], + current_value= 'jwt' if self.data.get('accessTokenAsJwt') else 'reference', + jans_help=self.myparent.get_help_from_schema(schema, 'accessTokenAsJwt'), style='green'), self.myparent.getTitledCheckBox( diff --git a/jans-cli-tui/utils.py b/jans-cli-tui/utils.py index 13e5f33861b..4dcad80ded5 100644 --- a/jans-cli-tui/utils.py +++ b/jans-cli-tui/utils.py @@ -18,6 +18,7 @@ def make_data_from_dialog(self): if hasattr(item, 'me'): me = item.me key_ = me.window.jans_name + #self.myparent.logger.debug(key_ + ': ' + str(type(me))) if isinstance(me, prompt_toolkit.widgets.base.TextArea): value_ = me.text elif isinstance(me, prompt_toolkit.widgets.base.Checkbox): From be62cac3a8e63acf27c911733989d846c08055b7 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Sun, 28 Aug 2022 12:43:34 -0700 Subject: [PATCH 070/364] fix jans-cli fixed data picker save --- .../models/oauth/edit_client_dialog.py | 2 +- jans-cli-tui/models/oauth/oauth.py | 8 +++- jans-cli-tui/utils.py | 3 -- .../wui_components/jans_data_picker.py | 38 +++++++------------ 4 files changed, 20 insertions(+), 31 deletions(-) diff --git a/jans-cli-tui/models/oauth/edit_client_dialog.py b/jans-cli-tui/models/oauth/edit_client_dialog.py index 617ab29e9e7..9af8a264334 100644 --- a/jans-cli-tui/models/oauth/edit_client_dialog.py +++ b/jans-cli-tui/models/oauth/edit_client_dialog.py @@ -308,7 +308,7 @@ def prepare_tabs(self): "Client Experiation Date", name='expirationDate', widget=DateSelectWidget( - value=self.data.get('expirationDate', "2022-11-05T14:45:26") + value=self.data.get('expirationDate', "2000-02-20T19:19:19") ), jans_help=self.myparent.get_help_from_schema(schema, 'expirationDate'), style='green' diff --git a/jans-cli-tui/models/oauth/oauth.py b/jans-cli-tui/models/oauth/oauth.py index 8e7c05adf54..cdc1dc9bfbe 100644 --- a/jans-cli-tui/models/oauth/oauth.py +++ b/jans-cli-tui/models/oauth/oauth.py @@ -228,10 +228,14 @@ def edit_scope(self, selected,event,size): ## enter self.edit_scope_dialog() def edit_client_dialog(self, **params): - + selected_line_data = params['data'] title = "Edit user Data (Clients)" - + + self.logger.debug("START") + self.logger.debug(selected_line_data) + self.logger.debug("END") + dialog = EditClientDialog(self, title=title, data=selected_line_data, save_handler=self.save_client) self.show_jans_dialog(dialog) diff --git a/jans-cli-tui/utils.py b/jans-cli-tui/utils.py index 4dcad80ded5..1891f2c91f5 100644 --- a/jans-cli-tui/utils.py +++ b/jans-cli-tui/utils.py @@ -12,13 +12,11 @@ class DialogUtils: def make_data_from_dialog(self): data = {} for tab in self.tabs: - self.myparent.logger.debug("********** {} ***********".format(str(tab))) for item in self.tabs[tab].children: if hasattr(item, 'me'): me = item.me key_ = me.window.jans_name - #self.myparent.logger.debug(key_ + ': ' + str(type(me))) if isinstance(me, prompt_toolkit.widgets.base.TextArea): value_ = me.text elif isinstance(me, prompt_toolkit.widgets.base.Checkbox): @@ -30,7 +28,6 @@ def make_data_from_dialog(self): elif isinstance(me, DropDownWidget): value_ = me.value elif isinstance(me,DateSelectWidget): - self.myparent.logger.debug("********** DateSelectWidget ***********") value_ = me.value data[key_] = value_ diff --git a/jans-cli-tui/wui_components/jans_data_picker.py b/jans-cli-tui/wui_components/jans_data_picker.py index 371f5e7062e..d8dfdfb2467 100644 --- a/jans-cli-tui/wui_components/jans_data_picker.py +++ b/jans-cli-tui/wui_components/jans_data_picker.py @@ -194,20 +194,20 @@ def __pt_container__(self): return self.container -class DateSelectWidget: # ex: data = "2023-11-27T14:05:35" - def __init__(self, value): +class DateSelectWidget: + def __init__(self, value): # ex: value = "2023-11-27T14:05:35" self.months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] + self.text = value # text >> showed in the widget #[:19]+ "T23:59:59" - if not value: - ts = time.strptime(data[:19], "%Y-%m-%dT%H:%M:%S") # "2023-11-27" - self.value = data + if value: + ts = time.strptime(value[:19], "%Y-%m-%dT%H:%M:%S") # "2023-11-27" self.date = time.strftime("%m/%d/%Y", ts) # "11/27/2023" - - self.text = self.date.split('/')[1] + '/' +self.months[int(self.date.split('/')[0])-1] + '/' +self.date.split('/')[2] - else: + else: + ### maybe here we can select today date!! self.date='' self.text = "Enter to Select" + self.value = str(value) self.dropdown = True self.window = Window( @@ -220,44 +220,34 @@ def __init__(self, value): self.select_box = JansSelectDate(date=self.date,months=self.months ) self.select_box_float = Float(content=self.select_box, xcursor=True, ycursor=True) - @property def value(self): - # with open('./hopa2.log', 'a') as f: - # f.write("_value: "+str("2022-11-05T14:45:26")) - # do some conversion staff and set to date_string in format YYYY-MM-DDTHH:mm:ss - return "2022-11-05T14:45:26" + return self.text @value.setter def value(self, value): - # with open('./hopa.log', 'a') as f: - # f.write("_value: "+str(value)) - # do some conversion staff convert value to internal date format and set to _value - self._vallue = "2022-11-05T14:45:26" + passed_value = self.value + + self._value = passed_value - def _get_text(self): if get_app().layout.current_window is self.window: return HTML('> <'.format('#00FF00', self.text)) return '> {} <'.format(self.text) - def _get_key_bindings(self): kb = KeyBindings() - def _focus_next(event): focus_next(event) @kb.add("enter") def _enter(event) -> None: - if self.select_box_float not in get_app().layout.container.floats: get_app().layout.container.floats.append( self.select_box_float) else: - self.text = str(self.select_box.entries[self.select_box.cord_y][self.select_box.cord_x] )+'/'+ str(self.select_box.months[self.select_box.current_month-1] ) +'/'+str(self.select_box.current_year) + self.text = str(self.select_box.current_year) +'-'+str(self.select_box.current_month-1) +'-'+str(self.select_box.entries[self.select_box.cord_y][self.select_box.cord_x] )+'T23:59:59' get_app().layout.container.floats.remove(self.select_box_float) - self.value = str(self.select_box.entries[self.select_box.cord_y][self.select_box.cord_x] )+'/'+ str(self.select_box.current_month) +'/'+str(self.select_box.current_year) +"T23:59:59" @kb.add("up") def _up(event): @@ -275,8 +265,6 @@ def _up(event): def _up(event): self.select_box.left() - - @kb.add("w") def _up(event): self.select_box.inc_year() From 5ea3c464468dcad23d9d3657c8fd65f1b2f8603e Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Mon, 29 Aug 2022 07:10:56 -0700 Subject: [PATCH 071/364] feat:jans-cli add time and Ability to change it --- .../wui_components/jans_data_picker.py | 137 +++++++++++++++--- 1 file changed, 117 insertions(+), 20 deletions(-) diff --git a/jans-cli-tui/wui_components/jans_data_picker.py b/jans-cli-tui/wui_components/jans_data_picker.py index d8dfdfb2467..40f0135c7b8 100644 --- a/jans-cli-tui/wui_components/jans_data_picker.py +++ b/jans-cli-tui/wui_components/jans_data_picker.py @@ -33,12 +33,7 @@ class JansSelectDate: def __init__(self, date='',months=[]): - if date != '': - self.date = date #"11/27/2023" - else: - today = datetime.date.today() - self.date = str(today.month) +'/' +str(today.day) +'/'+str(today.year) ## '11/27/2023' ## - + self.date = date #"11/27/2023" self.months = months self.cord_y = 0 @@ -197,15 +192,23 @@ def __pt_container__(self): class DateSelectWidget: def __init__(self, value): # ex: value = "2023-11-27T14:05:35" self.months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] - self.text = value # text >> showed in the widget #[:19]+ "T23:59:59" + # text >> showed in the widget if value: - ts = time.strptime(value[:19], "%Y-%m-%dT%H:%M:%S") # "2023-11-27" - self.date = time.strftime("%m/%d/%Y", ts) # "11/27/2023" + self.text = value + ts = time.strptime(value[:19], "%Y-%m-%dT%H:%M:%S") # "2023-11-27" + self.date = time.strftime("%m/%d/%Y", ts) # "11/27/2023" + self.houres = int(time.strftime("%H",ts)) + self.minutes =int(time.strftime("%M",ts)) + self.seconds = int(time.strftime("%S",ts)) else: - ### maybe here we can select today date!! - self.date='' + today = datetime.date.today() + self.date = str(today.month) +'/' +str(today.day) +'/'+str(today.year) ## '11/27/2023' ## self.text = "Enter to Select" + now = datetime.datetime.now() + self.houres = int(now.strftime("%H") ) + self.minutes =int(now.strftime("%M")) + self.seconds = int(now.strftime("%S")) self.value = str(value) @@ -222,13 +225,13 @@ def __init__(self, value): # ex: value = "2023-11-27T14:05:35" @property def value(self): - return self.text + if self.text != "Enter to Select": + return self.text @value.setter def value(self, value): - passed_value = self.value - - self._value = passed_value + #passed_value = self.value + self._value = self.value def _get_text(self): if get_app().layout.current_window is self.window: @@ -246,24 +249,118 @@ def _enter(event) -> None: if self.select_box_float not in get_app().layout.container.floats: get_app().layout.container.floats.append( self.select_box_float) else: - self.text = str(self.select_box.current_year) +'-'+str(self.select_box.current_month-1) +'-'+str(self.select_box.entries[self.select_box.cord_y][self.select_box.cord_x] )+'T23:59:59' + years = int(self.select_box.current_year) + months =int(self.select_box.current_month) + days = int(self.select_box.entries[self.select_box.cord_y][self.select_box.cord_x] ) + t = (years, months,days,self.houres,self.minutes,self.seconds,0,0,0) ## the up increment + t = time.mktime(t) + self.text= (time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(t))) get_app().layout.container.floats.remove(self.select_box_float) @kb.add("up") def _up(event): - self.select_box.up() + if self.select_box_float not in get_app().layout.container.floats: + ts = time.strptime(self.text[:19], "%Y-%m-%dT%H:%M:%S") # "2023-11-27" + years =int(time.strftime("%Y",ts)) + months = int(time.strftime("%m",ts)) + days = int(time.strftime("%d",ts)) + self.houres = int(time.strftime("%H",ts)) -7 ## it start from 0 to 23 + self.minutes =int(time.strftime("%M",ts)) + self.seconds = int(time.strftime("%S",ts)) + + t = (years, months,days,self.houres,self.minutes,self.seconds,0,0,0) ## the up increment + t = time.mktime(t) + self.text= (time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(t))) + + else : + self.select_box.up() + @kb.add("down") def _up(event): - self.select_box.down() + if self.select_box_float not in get_app().layout.container.floats: + ts = time.strptime(self.text[:19], "%Y-%m-%dT%H:%M:%S") # "2023-11-27" + years =int(time.strftime("%Y",ts)) + months = int(time.strftime("%m",ts)) + days = int(time.strftime("%d",ts)) + self.houres = int(time.strftime("%H",ts))-9 ## it start from 0 to 23 + self.minutes =int(time.strftime("%M",ts)) + self.seconds = int(time.strftime("%S",ts)) + t = (years, months,days,self.houres,self.minutes,self.seconds,0,0,0) ## the down increment + t = time.mktime(t) + self.text= (time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(t))) + + else : + self.select_box.down() @kb.add("right") def _up(event): - self.select_box.right() + if self.select_box_float not in get_app().layout.container.floats: + ts = time.strptime(self.text[:19], "%Y-%m-%dT%H:%M:%S") # "2023-11-27" + years =int(time.strftime("%Y",ts)) + months = int(time.strftime("%m",ts)) + days = int(time.strftime("%d",ts)) + self.houres = int(time.strftime("%H",ts)) -8 ## it start from 0 to 23 + self.minutes =int(time.strftime("%M",ts)) -1 + self.seconds = int(time.strftime("%S",ts)) + + t = (years, months,days,self.houres,self.minutes,self.seconds,0,0,0) ## the up increment + t = time.mktime(t) + self.text= (time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(t))) + else : + self.select_box.right() + @kb.add("left") def _up(event): - self.select_box.left() + if self.select_box_float not in get_app().layout.container.floats: + ts = time.strptime(self.text[:19], "%Y-%m-%dT%H:%M:%S") # "2023-11-27" + years =int(time.strftime("%Y",ts)) + months = int(time.strftime("%m",ts)) + days = int(time.strftime("%d",ts)) + self.houres = int(time.strftime("%H",ts)) -8 ## it start from 0 to 23 + self.minutes =int(time.strftime("%M",ts)) +1 + self.seconds = int(time.strftime("%S",ts)) + + t = (years, months,days,self.houres,self.minutes,self.seconds,0,0,0) ## the up increment + t = time.mktime(t) + self.text= (time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(t))) + + else : + self.select_box.left() + + + @kb.add("+") + def _up(event): + ts = time.strptime(self.text[:19], "%Y-%m-%dT%H:%M:%S") # "2023-11-27" + years =int(time.strftime("%Y",ts)) + months = int(time.strftime("%m",ts)) + days = int(time.strftime("%d",ts)) + self.houres = int(time.strftime("%H",ts)) -8 ## it start from 0 to 23 + self.minutes =int(time.strftime("%M",ts)) + self.seconds = int(time.strftime("%S",ts)) +1 + + t = (years, months,days,self.houres,self.minutes,self.seconds,0,0,0) ## the up increment + t = time.mktime(t) + self.text= (time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(t))) + + @kb.add("-") + def _up(event): + ts = time.strptime(self.text[:19], "%Y-%m-%dT%H:%M:%S") # "2023-11-27" + years =int(time.strftime("%Y",ts)) + months = int(time.strftime("%m",ts)) + days = int(time.strftime("%d",ts)) + self.houres = int(time.strftime("%H",ts)) -8 ## it start from 0 to 23 + self.minutes =int(time.strftime("%M",ts)) + self.seconds = int(time.strftime("%S",ts)) -1 + + t = (years, months,days,self.houres,self.minutes,self.seconds,0,0,0) ## the up increment + t = time.mktime(t) + self.text= (time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(t))) + + + + @kb.add("w") def _up(event): From bdbe90e818c941c196126e0dda0fe7a7f2184b81 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Mon, 29 Aug 2022 07:12:27 -0700 Subject: [PATCH 072/364] feat:jans-cli add PageUp and PageDown for navigation --- .../wui_components/jans_dialog_with_nav.py | 29 ++++++++++++++++++- .../wui_components/jans_side_nav_bar.py | 12 ++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/jans-cli-tui/wui_components/jans_dialog_with_nav.py b/jans-cli-tui/wui_components/jans_dialog_with_nav.py index 515ce0884af..54451c8065d 100644 --- a/jans-cli-tui/wui_components/jans_dialog_with_nav.py +++ b/jans-cli-tui/wui_components/jans_dialog_with_nav.py @@ -11,9 +11,11 @@ Dialog, VerticalLine, ) +from prompt_toolkit.key_binding import KeyBindings from prompt_toolkit.layout import ScrollablePane +from prompt_toolkit.application.current import get_app class JansDialogWithNav(): @@ -41,7 +43,7 @@ def create_window(self): ], width= (max_data_str )), VerticalLine(), ScrollablePane(content=self.content, height=height), - ], width=120, padding=1), + ], width=120, padding=1,key_bindings=self.get_nav_bar_key_bindings()), buttons=[ Button( @@ -53,6 +55,31 @@ def create_window(self): ) #--------------------------------------------------------------------------------------# + + def get_nav_bar_key_bindings(self): + kb = KeyBindings() + + @kb.add("pageup") + def _go_up(event) -> None: + app = get_app() + self.navbar.go_up() + app.layout.focus(self.navbar) + + @kb.add("pagedown") + def _go_up(event) -> None: + app = get_app() + self.navbar.go_down() + app.layout.focus(self.navbar) + + + + + @kb.add("enter") + def _(event): + pass + + return kb + def __pt_container__(self): return self.dialog diff --git a/jans-cli-tui/wui_components/jans_side_nav_bar.py b/jans-cli-tui/wui_components/jans_side_nav_bar.py index 2b7d2946067..108657081be 100644 --- a/jans-cli-tui/wui_components/jans_side_nav_bar.py +++ b/jans-cli-tui/wui_components/jans_side_nav_bar.py @@ -60,6 +60,16 @@ def update_selection(self): self.selection_changed(self.cur_tab) + def go_up(self): + self.cur_navbar_selection = ( + self.cur_navbar_selection - 1) % len(self.navbar_entries) + self.update_selection() + + def go_down(self): + self.cur_navbar_selection = ( + self.cur_navbar_selection + 1) % len(self.navbar_entries) + self.update_selection() + def get_nav_bar_key_bindings(self): kb = KeyBindings() @@ -75,6 +85,8 @@ def _go_up(event) -> None: self.cur_navbar_selection + 1) % len(self.navbar_entries) self.update_selection() + + @kb.add("enter") def _(event): pass From 61b4ec5a23f1058d40f674383562bb8ed86c7df2 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Mon, 29 Aug 2022 07:13:25 -0700 Subject: [PATCH 073/364] fix:jans-cli removed custom date in datapicker --- jans-cli-tui/models/oauth/edit_client_dialog.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/jans-cli-tui/models/oauth/edit_client_dialog.py b/jans-cli-tui/models/oauth/edit_client_dialog.py index 9af8a264334..b75c0ce1c44 100644 --- a/jans-cli-tui/models/oauth/edit_client_dialog.py +++ b/jans-cli-tui/models/oauth/edit_client_dialog.py @@ -63,7 +63,7 @@ def cancel(): self.dialog = JansDialogWithNav( title=title, - navbar=DynamicContainer(lambda:self.side_nav_bar), + navbar=self.side_nav_bar, content=DynamicContainer(lambda: self.tabs[self.left_nav]), button_functions=[ (save, "Save"), @@ -78,8 +78,6 @@ def prepare_tabs(self): schema = self.myparent.cli_object.get_schema_from_reference('#/components/schemas/Client') - # self.data['expirationDate'] = 1331856000000 ## to test widget - self.tabs = OrderedDict() self.tabs['Basic'] = HSplit([ @@ -308,7 +306,7 @@ def prepare_tabs(self): "Client Experiation Date", name='expirationDate', widget=DateSelectWidget( - value=self.data.get('expirationDate', "2000-02-20T19:19:19") + value=self.data.get('expirationDate', "") ), jans_help=self.myparent.get_help_from_schema(schema, 'expirationDate'), style='green' @@ -318,8 +316,6 @@ def prepare_tabs(self): ],width=D() ) - - self.tabs['Client Scripts'] = HSplit([ From 0165e3ef947400545feb4079cb14f390975d3f63 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Tue, 30 Aug 2022 11:17:09 +0300 Subject: [PATCH 074/364] fix: jans-cli width of data display dialog --- jans-cli-tui/jans-cli-tui.py | 2 +- jans-cli-tui/wui_components/jans_cli_dialog.py | 12 ++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index fde510da130..70d46a9728b 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -481,7 +481,7 @@ def data_display_dialog(self, **params): ) ]) - dialog = JansGDialog(self, title=params['selected'][0], body=body) + dialog = JansGDialog(self, title=params['selected'][0], body=body, width=int(self.width*0.9)) self.show_jans_dialog(dialog) diff --git a/jans-cli-tui/wui_components/jans_cli_dialog.py b/jans-cli-tui/wui_components/jans_cli_dialog.py index 0409df0b7d5..77a1b6e9e30 100644 --- a/jans-cli-tui/wui_components/jans_cli_dialog.py +++ b/jans-cli-tui/wui_components/jans_cli_dialog.py @@ -1,16 +1,12 @@ import json +from functools import partial from asyncio import Future -from prompt_toolkit.widgets import Button, Dialog, TextArea -from prompt_toolkit.layout.containers import HSplit -from prompt_toolkit.application.current import get_app +from prompt_toolkit.widgets import Button, Dialog from prompt_toolkit.layout.dimension import D -from static import DialogResult -from functools import partial - class JansGDialog: - def __init__(self, parent, title, body, buttons=[]): + def __init__(self, parent, title, body, buttons=[], width=D(preferred=80)): self.future = Future() self.body = body self.myparent = parent @@ -30,7 +26,7 @@ def do_handler(button_text, handler): title=title, body=body, buttons=buttons, - width=D(preferred=80), + width=width, modal=True, ) From eb554ee8afbdb81a837952b91819a67935b8b9dd Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Tue, 30 Aug 2022 11:32:17 +0300 Subject: [PATCH 075/364] fix: jans-cli dialog focus --- jans-cli-tui/jans-cli-tui.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index 70d46a9728b..6cdee8b6f56 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -454,15 +454,13 @@ async def show_dialog_as_float(self, dialog): def show_jans_dialog(self, dialog): async def coroutine(): - app = get_app() - focused_before = app.layout.current_window + focused_before = self.layout.current_window self.layout.focus(dialog) result = await self.show_dialog_as_float(dialog) try: - app.layout.focus(focused_before) + self.layout.focus(focused_before) except: - app.layout.focus(self.center_frame) - + self.layout.focus(self.center_frame) return result @@ -507,13 +505,12 @@ def show_message(self, title, message, buttons=[]): body = HSplit([Label(message)]) dialog = JansMessageDialog(title=title, body=body, buttons=buttons) - app = get_app() - focused_before = app.layout.current_window + focused_before = self.layout.current_window float_ = Float(content=dialog) self.root_layout.floats.append(float_) dialog.me = float_ dialog.focus_on_exit = focused_before - app.layout.focus(dialog) + self.layout.focus(dialog) self.press_tab() def show_again(self): ## nasted dialog Button From ae9e9dff4c9ed90b24b1b454c85c9506a09d22c8 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Tue, 30 Aug 2022 11:36:29 +0300 Subject: [PATCH 076/364] fix: jans-cli code cleaning --- jans-cli-tui/jans-cli-tui.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index 6cdee8b6f56..b10389875e4 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -74,11 +74,8 @@ class JansCliApp(Application, JansAuthServer): def __init__(self): self.init_logger() - self.app_started = False self.status_bar_text = '' self.width, self.height = get_terminal_size() - self.app = get_app() - self.show_dialog = False ## ## ## ## self.set_keybindings() # -------------------------------------------------------------------------------- # @@ -93,7 +90,6 @@ def __init__(self): FormattedTextControl(self.update_status_bar), style="class:status", height=1 ) - JansAuthServer.initialize(self) @@ -137,9 +133,6 @@ def __init__(self): mouse_support=True, ## added ) - - - self.app_started = True self.main_nav_selection_changed(self.nav_bar.navbar_entries[0][0]) # Since first module is oauth, set center frame to my oauth main container. @@ -479,7 +472,7 @@ def data_display_dialog(self, **params): ) ]) - dialog = JansGDialog(self, title=params['selected'][0], body=body, width=int(self.width*0.9)) + dialog = JansGDialog(self, title=params['selected'][0], body=body, width=self.dialog_width) self.show_jans_dialog(dialog) From f3ac519e2b05f3e6e3551726bd7ff702c5add0d1 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Tue, 30 Aug 2022 12:11:26 +0300 Subject: [PATCH 077/364] fix: jans-cli sizing via output --- jans-cli-tui/jans-cli-tui.py | 15 +++++++++------ jans-cli-tui/wui_components/jans_cli_dialog.py | 5 ++++- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index b10389875e4..9bc47d10e83 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -5,7 +5,6 @@ import os import logging -from shutil import get_terminal_size import time from asyncio import Future, ensure_future from pynput.keyboard import Key, Controller @@ -75,13 +74,9 @@ class JansCliApp(Application, JansAuthServer): def __init__(self): self.init_logger() self.status_bar_text = '' - self.width, self.height = get_terminal_size() self.set_keybindings() # -------------------------------------------------------------------------------- # - self.dialog_width = int(self.width*0.9) # 120 ## to be dynamic - self.dialog_height = int(self.height*0.8) ## to be dynamic - self.keyboard = Controller() self.yes_button = Button(text="Yes", handler=accept_yes) @@ -142,7 +137,15 @@ def __init__(self): # ----------------------------------------------------------------------------- # self.check_jans_cli_ini() # ----------------------------------------------------------------------------- # + self.logger.debug(str(self.output.get_size())) + + @property + def dialog_width(self): + return int(self.output.get_size().rows*0.8) + @property + def dialog_height(self): + return int(self.output.get_size().columns*0.9) def init_logger(self): self.logger = logging.getLogger('JansCli') @@ -472,7 +475,7 @@ def data_display_dialog(self, **params): ) ]) - dialog = JansGDialog(self, title=params['selected'][0], body=body, width=self.dialog_width) + dialog = JansGDialog(self, title=params['selected'][0], body=body) self.show_jans_dialog(dialog) diff --git a/jans-cli-tui/wui_components/jans_cli_dialog.py b/jans-cli-tui/wui_components/jans_cli_dialog.py index 77a1b6e9e30..7dbe10edf47 100644 --- a/jans-cli-tui/wui_components/jans_cli_dialog.py +++ b/jans-cli-tui/wui_components/jans_cli_dialog.py @@ -6,11 +6,14 @@ from prompt_toolkit.layout.dimension import D class JansGDialog: - def __init__(self, parent, title, body, buttons=[], width=D(preferred=80)): + def __init__(self, parent, title, body, buttons=[], width=None): self.future = Future() self.body = body self.myparent = parent + if not width: + width = int(parent.output.get_size().columns * 0.85) + if not buttons: buttons = [Button(text="OK")] From 169b59d468c7c534478e82c11335a39f81f0eed8 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Tue, 30 Aug 2022 14:08:27 +0300 Subject: [PATCH 078/364] fix: jans-cli client properties --- jans-cli-tui/jans-cli-tui.py | 1 - .../models/oauth/edit_client_dialog.py | 79 ++++++++++--------- 2 files changed, 42 insertions(+), 38 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index 9bc47d10e83..1e18226574a 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -137,7 +137,6 @@ def __init__(self): # ----------------------------------------------------------------------------- # self.check_jans_cli_ini() # ----------------------------------------------------------------------------- # - self.logger.debug(str(self.output.get_size())) @property def dialog_width(self): diff --git a/jans-cli-tui/models/oauth/edit_client_dialog.py b/jans-cli-tui/models/oauth/edit_client_dialog.py index b75c0ce1c44..72b978e2a2f 100644 --- a/jans-cli-tui/models/oauth/edit_client_dialog.py +++ b/jans-cli-tui/models/oauth/edit_client_dialog.py @@ -33,7 +33,7 @@ def save(): self.data = self.make_data_from_dialog() self.data['disabled'] = not self.data['disabled'] - for list_key in ('redirectUris', 'scopes', 'postLogoutRedirectUris'): + for list_key in ('redirectUris', 'scopes', 'postLogoutRedirectUris', 'requestUris', 'spontaneousScopes', 'contacts'): if self.data[list_key]: self.data[list_key] = self.data[list_key].splitlines() @@ -193,14 +193,19 @@ def prepare_tabs(self): #self.myparent.getTitledText(title ="Policy URI", name='policyUri', value=self.data.get('policyUri',''),style='green'), #self.myparent.getTitledText(title ="Logo URI", name='logoUri', value=self.data.get('logoUri',''),style='green'), #self.myparent.getTitledText(title ="Term of service URI", name='tosUri', value=self.data.get('tosUri',''),style='green'), - self.myparent.getTitledText(title ="Contacts", name='contacts', value=self.data.get('contacts',''),style='green'), - VSplit([ - Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3,) - ]), - self.myparent.getTitledText(title ="Authorized JS origins", name='authorizedOrigins', value=self.data.get('authorizedOrigins',''),style='green'), - VSplit([ - Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3) - ]), + + self.myparent.getTitledText("Contacts", + name='contacts', + value='\n'.join(self.data.get('contacts', [])), + height=3, + style='green'), + + self.myparent.getTitledText("Authorized JS origins", + name='authorizedOrigins', + value='\n'.join(self.data.get('authorizedOrigins', [])), + height=3, + style='green'), + self.myparent.getTitledText(title ="Software id", name='softwareId', value=self.data.get('softwareId',''),style='green'), self.myparent.getTitledText(title ="Software version", name='softwareVersion', value=self.data.get('softwareVersion',''), style='green'), self.myparent.getTitledText(title ="Software statement", name='softwareStatement', value=self.data.get('softwareStatement',''), style='green'), @@ -214,14 +219,19 @@ def prepare_tabs(self): self.myparent.getTitledCheckBox("Require user code param", name='backchannelUserCodeParameterSupported', checked=self.data.get('backchannelUserCodeParameterSupported'),style='green'), Label(text="PAR",style='bold'), - + self.myparent.getTitledText(title ="Request lifetime", name='parLifetime', value=self.data.get('parLifetime',''),style='green'), self.myparent.getTitledCheckBox("Request PAR", name='sessionIdRequestParameterEnabled', checked=self.data.get('sessionIdRequestParameterEnabled'),style='green'), - - Label("UMA",style='bold'), - self.myparent.getTitledRadioButton("PRT token type", name='applicationType!', values=['JWT', 'Reference'], current_value=self.data.get('applicationType!'),style='green'), - self.myparent.getTitledText(title ="Claims redirect URI", name='claimRedirectUris', value=self.data.get('claimRedirectUris',''),style='green'), + Label("UMA", style='bold'), + + self.myparent.getTitledRadioButton( + "PRT token type", + name='applicationType!', + values=['JWT', 'Reference'], + current_value=self.data.get('applicationType!'), style='green'), + + self.myparent.getTitledText(title ="Claims redirect URI", name='claimRedirectUris', value=self.data.get('claimRedirectUris',''),style='green'), self.myparent.getTitledText("RPT Mofification Script", name='rptClaimsScripts', @@ -271,35 +281,31 @@ def prepare_tabs(self): self.tabs['Advanced Client Properties'] = HSplit([ self.myparent.getTitledCheckBox("Default Prompt login", name='defaultPromptLogin', checked=self.data.get('defaultPromptLogin'),style='green'), - VSplit([ - self.myparent.getTitledCheckBox("Persist Authorizations", name='persistClientAuthorizations', checked=self.data.get('persistClientAuthorizations'),style='green'), - # self.myparent.getTitledCheckBox("Keep expired", name='Supress', checked=self.data.get('Supress'),style='green'), - ]) , + self.myparent.getTitledCheckBox("Persist Authorizations", name='persistClientAuthorizations', checked=self.data.get('persistClientAuthorizations'),style='green'), self.myparent.getTitledCheckBox("Allow spontaneos scopes", name='allowSpontaneousScopes', checked=self.data.get('allowSpontaneousScopes'),style='green'), self.myparent.getTitledText("spontaneos scopes validation regex", name='spontaneousScopes', value=self.data.get('spontaneousScopes',''),style='green'), - VSplit([ - Label(text="Spontaneous Scopes",style='green'), - Button("view current", handler=self.myparent.show_again,left_symbol='',right_symbol='',) - - ]) , + + self.myparent.getTitledText("Spontaneous Scopes", + name='spontaneousScopes', + value='\n'.join(self.data.get('spontaneousScopes', [])), + height=3, + style='green'), + self.myparent.getTitledText("Initial Login URI", name='initiateLoginUri', value=self.data.get('initiateLoginUri',''),style='green'), - VSplit([ - self.myparent.getTitledText("Request URIs", name='requestUris', value=self.data.get('requestUris',''),style='green'), - Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3,) - ]) , + self.myparent.getTitledText("Request URIs", + name='requestUris', + value='\n'.join(self.data.get('requestUris', [])), + height=3, + style='green'), self.myparent.getTitledText("Default ACR", - name='authorizedAcrValues', - value='\n'.join(self.data.get('authorizedAcrValues', [])), - height=3, - style='green'), - - VSplit([ - # self.myparent.getTitledText("Allowed ACRs", name='clientSecret', value=self.data.get('clientSecret',''),style='green'), - Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3,) - ]) , + name='authorizedAcrValues', + value='\n'.join(self.data.get('authorizedAcrValues', [])), + height=3, + style='green'), + self.myparent.getTitledText("TLS Subject DN", name='x5c', value=self.data.get('x5c',''),style='green'), self.myparent.getTitledWidget( @@ -312,7 +318,6 @@ def prepare_tabs(self): style='green' ), - ],width=D() ) From 4f6cfa1224ec11041583c360b54adc8333bf1ea3 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Tue, 30 Aug 2022 14:19:13 +0300 Subject: [PATCH 079/364] fix: jans-cli string to list --- .../models/oauth/edit_client_dialog.py | 49 +++++++++++-------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/jans-cli-tui/models/oauth/edit_client_dialog.py b/jans-cli-tui/models/oauth/edit_client_dialog.py index 72b978e2a2f..96e9cc134ca 100644 --- a/jans-cli-tui/models/oauth/edit_client_dialog.py +++ b/jans-cli-tui/models/oauth/edit_client_dialog.py @@ -33,7 +33,15 @@ def save(): self.data = self.make_data_from_dialog() self.data['disabled'] = not self.data['disabled'] - for list_key in ('redirectUris', 'scopes', 'postLogoutRedirectUris', 'requestUris', 'spontaneousScopes', 'contacts'): + for list_key in ( + 'redirectUris', + 'scopes', + 'postLogoutRedirectUris', + 'contacts', + 'authorizedOrigins', + 'rptClaimsScripts', + 'claimRedirectUris', + ): if self.data[list_key]: self.data[list_key] = self.data[list_key].splitlines() @@ -82,12 +90,13 @@ def prepare_tabs(self): self.tabs['Basic'] = HSplit([ self.myparent.getTitledText( - title ="Client_ID", - name='inum', - value=self.data.get('inum',''), - jans_help=self.myparent.get_help_from_schema(schema, 'inum'), + "Client_ID", + name='inum', + value=self.data.get('inum',''), + jans_help=self.myparent.get_help_from_schema(schema, 'inum'), read_only=True, - style='green'), + style='green'), + self.myparent.getTitledCheckBox("Active", name='disabled', checked= not self.data.get('disabled'), jans_help=self.myparent.get_help_from_schema(schema, 'disabled'), style='green'), self.myparent.getTitledText("Client Name", name='displayName', value=self.data.get('displayName',''), jans_help=self.myparent.get_help_from_schema(schema, 'displayName'), style='green'), self.myparent.getTitledText("Client Secret", name='clientSecret', value=self.data.get('clientSecret',''), jans_help=self.myparent.get_help_from_schema(schema, 'clientSecret'), style='green'), @@ -105,8 +114,8 @@ def prepare_tabs(self): self.myparent.getTitledRadioButton( "Subject Type", name='subjectType', - values=[('public', 'Public'),('pairwise', 'Pairwise')], - current_value=self.data.get('subjectType'), + values=[('public', 'Public'),('pairwise', 'Pairwise')], + current_value=self.data.get('subjectType'), jans_help=self.myparent.get_help_from_schema(schema, 'subjectType'), style='green'), self.myparent.getTitledCheckBoxList( @@ -131,10 +140,10 @@ def prepare_tabs(self): self.myparent.getTitledText("Redirect Uris", name='redirectUris', value='\n'.join(self.data.get('redirectUris', [])), height=3, style='class:required-field'), self.myparent.getTitledText("Redirect Regex", name='redirectUrisRegex', value=self.data.get('redirectUrisRegex', ''), style='green'), self.myparent.getTitledText("Scopes", - name='scopes', - value='\n'.join(self.data.get('scopes', [])), - height=3, - style='green'), + name='scopes', + value='\n'.join(self.data.get('scopes', [])), + height=3, + style='green'), ],width=D(), ) @@ -234,16 +243,16 @@ def prepare_tabs(self): self.myparent.getTitledText(title ="Claims redirect URI", name='claimRedirectUris', value=self.data.get('claimRedirectUris',''),style='green'), self.myparent.getTitledText("RPT Mofification Script", - name='rptClaimsScripts', - value='\n'.join(self.data.get('rptClaimsScripts', [])), - height=3, - style='green'), + name='rptClaimsScripts', + value='\n'.join(self.data.get('rptClaimsScripts', [])), + height=3, + style='green'), self.myparent.getTitledText("Claims Gathering Script", - name='claimRedirectUris', - value='\n'.join(self.data.get('claimRedirectUris', [])), - height=3, - style='green'), + name='claimRedirectUris', + value='\n'.join(self.data.get('claimRedirectUris', [])), + height=3, + style='green'), Label(text="tabel",style='blue'), ## TODO with Jans VerticalNav From 65f2ed421817b7cd28022f22d96f0aa24cd16bd3 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Tue, 30 Aug 2022 16:49:40 +0300 Subject: [PATCH 080/364] fix: jans-cli attribute names in oxauth-client --- jans-cli-tui/models/oauth/edit_client_dialog.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/jans-cli-tui/models/oauth/edit_client_dialog.py b/jans-cli-tui/models/oauth/edit_client_dialog.py index 96e9cc134ca..f918df66871 100644 --- a/jans-cli-tui/models/oauth/edit_client_dialog.py +++ b/jans-cli-tui/models/oauth/edit_client_dialog.py @@ -48,6 +48,9 @@ def save(): if 'accessTokenAsJwt' in self.data: self.data['accessTokenAsJwt'] = self.data['accessTokenAsJwt'] == 'jwt' + if 'rptAsJwt' in self.data: + self.data['rptAsJwt'] = self.data['rptAsJwt'] == 'jwt' + cfr = self.check_required_fields() self.myparent.logger.debug("CFR: "+str(cfr)) if not cfr: @@ -236,9 +239,10 @@ def prepare_tabs(self): self.myparent.getTitledRadioButton( "PRT token type", - name='applicationType!', - values=['JWT', 'Reference'], - current_value=self.data.get('applicationType!'), style='green'), + name='rptAsJwt!', + values=[('jwt', 'JWT'), ('reference', 'Reference')], + current_value='jwt' if self.data.get('rptAsJwt') else 'reference', + style='green'), self.myparent.getTitledText(title ="Claims redirect URI", name='claimRedirectUris', value=self.data.get('claimRedirectUris',''),style='green'), From a693e776cd3a85fcd8803fee91794c18b5eb580d Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Tue, 30 Aug 2022 20:46:56 +0300 Subject: [PATCH 081/364] fix: jans-cli enable-disable spontaneousScopes according to allowSpontaneousScopes --- jans-cli-tui/jans-cli-tui.py | 11 +++- .../models/oauth/edit_client_dialog.py | 53 ++++++++++++------- 2 files changed, 44 insertions(+), 20 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index 1e18226574a..70c94151fd4 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -361,12 +361,21 @@ def getTitledCheckBoxList(self, title, name, values, current_values=[], jans_hel return v - def getTitledCheckBox(self, title, name, text='', checked=False, jans_help='', style=''): + def getTitledCheckBox(self, title, name, text='', checked=False, on_selection_changed=None, jans_help='', style=''): title += ': ' cb = Checkbox(text) cb.checked = checked cb.window.jans_name = name cb.window.jans_help = jans_help + + handler_org = cb._handle_enter + def custom_handler(): + handler_org() + on_selection_changed(cb) + + if on_selection_changed: + cb._handle_enter = custom_handler + #li, cd, width = self.handle_long_string(title, text, cb) v = VSplit([Label(text=title, width=len(title), style=style, wrap_lines=False), cb]) diff --git a/jans-cli-tui/models/oauth/edit_client_dialog.py b/jans-cli-tui/models/oauth/edit_client_dialog.py index f918df66871..c4b4af10286 100644 --- a/jans-cli-tui/models/oauth/edit_client_dialog.py +++ b/jans-cli-tui/models/oauth/edit_client_dialog.py @@ -84,9 +84,10 @@ def cancel(): width=self.myparent.dialog_width, ) + def prepare_tabs(self): - + schema = self.myparent.cli_object.get_schema_from_reference('#/components/schemas/Client') self.tabs = OrderedDict() @@ -150,7 +151,7 @@ def prepare_tabs(self): ],width=D(), ) - + self.tabs['Tokens'] = HSplit([ self.myparent.getTitledRadioButton( "Access Token Type", @@ -188,7 +189,7 @@ def prepare_tabs(self): self.myparent.getTitledText("Defult max authn age", name='defaultMaxAge', value=self.data.get('defaultMaxAge',''),style='green'), ],width=D()) - + self.tabs['Logout'] = HSplit([ self.myparent.getTitledText("Front channel logout URI", name='frontChannelLogoutUri', value=self.data.get('frontChannelLogoutUri',''), style='green'), @@ -199,7 +200,7 @@ def prepare_tabs(self): ],width=D() ) - + self.tabs['SoftwareInfo'] = HSplit([ #self.myparent.getTitledText(title ="Client URI", name='clientUri', value=self.data.get('clientUri',''),style='green'), #self.myparent.getTitledText(title ="Policy URI", name='policyUri', value=self.data.get('policyUri',''),style='green'), @@ -251,19 +252,19 @@ def prepare_tabs(self): value='\n'.join(self.data.get('rptClaimsScripts', [])), height=3, style='green'), - + self.myparent.getTitledText("Claims Gathering Script", name='claimRedirectUris', value='\n'.join(self.data.get('claimRedirectUris', [])), height=3, style='green'), - + Label(text="tabel",style='blue'), ## TODO with Jans VerticalNav - + ] ) - + self.tabs['Encryption/Signing'] = HSplit([ self.myparent.getTitledText(title ="Client JWKS URI", name='jwksUri', value=self.data.get('jwksUri',''),style='green'), self.myparent.getTitledText(title ="Client JWKS", name='jwks', value=self.data.get('jwks',''),style='green'), @@ -287,23 +288,37 @@ def prepare_tabs(self): Label(text="Request Object"), Label(text="a, b, c",style='red'), ]), - + ] ) - + + def allow_spontaneous_changed(cb): + self.spontaneous_scopes.me.read_only = not cb.checked + self.spontaneous_scopes.me.focusable = cb.checked + self.myparent.logger.debug("CB-allow-spontaneous: " + str(cb.checked) ) + self.myparent.invalidate() + + self.spontaneous_scopes = self.myparent.getTitledText( + "Spontaneos scopes validation regex", + name='spontaneousScopes', + value=self.data.get('spontaneousScopes',''), + read_only=False if 'allowSpontaneousScopes' in self.data and self.data['allowSpontaneousScopes'] else True, + style='green') + + self.tabs['Advanced Client Properties'] = HSplit([ - self.myparent.getTitledCheckBox("Default Prompt login", name='defaultPromptLogin', checked=self.data.get('defaultPromptLogin'),style='green'), - self.myparent.getTitledCheckBox("Persist Authorizations", name='persistClientAuthorizations', checked=self.data.get('persistClientAuthorizations'),style='green'), - self.myparent.getTitledCheckBox("Allow spontaneos scopes", name='allowSpontaneousScopes', checked=self.data.get('allowSpontaneousScopes'),style='green'), + self.myparent.getTitledCheckBox("Default Prompt login", name='defaultPromptLogin', checked=self.data.get('defaultPromptLogin'), style='green'), + self.myparent.getTitledCheckBox("Persist Authorizations", name='persistClientAuthorizations', checked=self.data.get('persistClientAuthorizations'), style='green'), + self.myparent.getTitledCheckBox("Allow spontaneos scopes", name='allowSpontaneousScopes', checked=self.data.get('allowSpontaneousScopes'), on_selection_changed=allow_spontaneous_changed, style='green'), - self.myparent.getTitledText("spontaneos scopes validation regex", name='spontaneousScopes', value=self.data.get('spontaneousScopes',''),style='green'), + self.spontaneous_scopes, - self.myparent.getTitledText("Spontaneous Scopes", - name='spontaneousScopes', - value='\n'.join(self.data.get('spontaneousScopes', [])), - height=3, - style='green'), + #self.myparent.getTitledText("Spontaneous Scopes", + # name='spontaneousScopes', + # value='\n'.join(self.data.get('spontaneousScopes', [])), + # height=3, + # style='green'), self.myparent.getTitledText("Initial Login URI", name='initiateLoginUri', value=self.data.get('initiateLoginUri',''),style='green'), From 42567828a621516ddee875571085fb28429ea067 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Tue, 30 Aug 2022 22:37:21 +0300 Subject: [PATCH 082/364] fix: jans-cli readonly field --- jans-cli-tui/jans-cli-tui.py | 9 ++++++--- jans-cli-tui/models/oauth/edit_client_dialog.py | 6 +++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index 70c94151fd4..61c986b3e9d 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -74,6 +74,8 @@ class JansCliApp(Application, JansAuthServer): def __init__(self): self.init_logger() self.status_bar_text = '' + self.styles = dict(style.style_rules) + self.set_keybindings() # -------------------------------------------------------------------------------- # @@ -324,17 +326,18 @@ def handle_long_string (self, text, values, widget): return new_title , cd , width - def getTitledText(self, title, name, value='', height=1, jans_help='', accept_handler=None, read_only=False, width=None, style=''): + def getTitledText(self, title, name, value='', height=1, jans_help='', accept_handler=None, read_only=False, focusable=None, width=None, style=''): title += ': ' ta = TextArea( text=str(value), multiline=height > 1, height=height, read_only=read_only, - style="class:textarea-readonly" if read_only else "class:textarea", - focusable=not read_only, + style=self.styles['textarea-readonly'] if read_only else self.styles['textarea'], accept_handler=accept_handler, + focusable=not read_only if focusable is None else focusable, ) + ta.window.jans_name = name ta.window.jans_help = jans_help diff --git a/jans-cli-tui/models/oauth/edit_client_dialog.py b/jans-cli-tui/models/oauth/edit_client_dialog.py index c4b4af10286..b9b4d3a9159 100644 --- a/jans-cli-tui/models/oauth/edit_client_dialog.py +++ b/jans-cli-tui/models/oauth/edit_client_dialog.py @@ -293,16 +293,16 @@ def prepare_tabs(self): ) def allow_spontaneous_changed(cb): + self.spontaneous_scopes.me.window.style = 'underline ' + (self.myparent.styles['textarea'] if cb.checked else self.myparent.styles['textarea-readonly']) + self.spontaneous_scopes.me.text = '' self.spontaneous_scopes.me.read_only = not cb.checked - self.spontaneous_scopes.me.focusable = cb.checked - self.myparent.logger.debug("CB-allow-spontaneous: " + str(cb.checked) ) - self.myparent.invalidate() self.spontaneous_scopes = self.myparent.getTitledText( "Spontaneos scopes validation regex", name='spontaneousScopes', value=self.data.get('spontaneousScopes',''), read_only=False if 'allowSpontaneousScopes' in self.data and self.data['allowSpontaneousScopes'] else True, + focusable=True, style='green') From 0f4ea150df6555bc25a356824c75700bdb40700f Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Tue, 30 Aug 2022 13:58:09 -0700 Subject: [PATCH 083/364] before merge --- jans-cli-tui/jans-cli-tui.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index fde510da130..07ee873d7dd 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -35,13 +35,12 @@ Button, Frame, Label, - RadioList, TextArea, - CheckboxList, - Checkbox, + CheckboxList, + Checkbox, + RadioList, ) -# -------------------------------------------------------------------------- # from cli import config_cli from wui_components.jans_cli_dialog import JansGDialog from wui_components.jans_nav_bar import JansNavBar @@ -53,6 +52,9 @@ from pathlib import Path # -------------------------------------------------------------------------- # +from prompt_toolkit.key_binding.key_processor import KeyPressEvent + + home_dir = Path.home() config_dir = home_dir.joinpath('.config') @@ -328,7 +330,7 @@ def handle_long_string (self, text, values, widget): pass return new_title , cd , width - + def getTitledText(self, title, name, value='', height=1, jans_help='', accept_handler=None, read_only=False, width=None, style=''): title += ': ' ta = TextArea( @@ -365,7 +367,6 @@ def getTitledCheckBoxList(self, title, name, values, current_values=[], jans_hel return v - def getTitledCheckBox(self, title, name, text='', checked=False, jans_help='', style=''): title += ': ' cb = Checkbox(text) @@ -395,7 +396,6 @@ def getTitledRadioButton(self, title, name, values, current_value=None, jans_hel return v - def getTitledWidget(self, title, name, widget, jans_help='', style=''): widget.window.jans_name = name widget.window.jans_help = jans_help From 7546d290554beba00a1f527cafddcaa59c681726 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Tue, 30 Aug 2022 14:16:08 -0700 Subject: [PATCH 084/364] fix:jans-cli just Merging --- jans-cli-tui/models/oauth/edit_client_dialog.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/jans-cli-tui/models/oauth/edit_client_dialog.py b/jans-cli-tui/models/oauth/edit_client_dialog.py index b9b4d3a9159..30c2dd43287 100644 --- a/jans-cli-tui/models/oauth/edit_client_dialog.py +++ b/jans-cli-tui/models/oauth/edit_client_dialog.py @@ -83,7 +83,7 @@ def cancel(): height=self.myparent.dialog_height, width=self.myparent.dialog_width, ) - + def prepare_tabs(self): @@ -399,7 +399,7 @@ def allow_spontaneous_changed(cb): self.left_nav = list(self.tabs.keys())[0] - + def client_dialog_nav_selection_changed(self, selection): self.left_nav = selection @@ -415,6 +415,3 @@ def __pt_container__(self): - - - From 254b7e156002ebc7991a5d04d74792bb9d4aa148 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Tue, 30 Aug 2022 14:17:51 -0700 Subject: [PATCH 085/364] fix:jans-cli just Merging --- jans-cli-tui/wui_components/jans_cli_dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jans-cli-tui/wui_components/jans_cli_dialog.py b/jans-cli-tui/wui_components/jans_cli_dialog.py index 7dbe10edf47..5803776976d 100644 --- a/jans-cli-tui/wui_components/jans_cli_dialog.py +++ b/jans-cli-tui/wui_components/jans_cli_dialog.py @@ -34,4 +34,4 @@ def do_handler(button_text, handler): ) def __pt_container__(self): - return self.dialog + return self.dialog \ No newline at end of file From 3e4c49084dd0578d82f68f63f4184ca391fabd01 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Tue, 30 Aug 2022 14:18:22 -0700 Subject: [PATCH 086/364] feat:jans-cli add pageup and pagedown navigation --- .../wui_components/jans_dialog_with_nav.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/jans-cli-tui/wui_components/jans_dialog_with_nav.py b/jans-cli-tui/wui_components/jans_dialog_with_nav.py index 54451c8065d..575d2f3b59e 100644 --- a/jans-cli-tui/wui_components/jans_dialog_with_nav.py +++ b/jans-cli-tui/wui_components/jans_dialog_with_nav.py @@ -42,8 +42,10 @@ def create_window(self): self.navbar ], width= (max_data_str )), VerticalLine(), - ScrollablePane(content=self.content, height=height), - ], width=120, padding=1,key_bindings=self.get_nav_bar_key_bindings()), + VSplit([ + ScrollablePane(content=self.content, height=height), + ],key_bindings=self.get_nav_bar_key_bindings()) + ], width=120, padding=1), buttons=[ Button( @@ -59,25 +61,18 @@ def create_window(self): def get_nav_bar_key_bindings(self): kb = KeyBindings() - @kb.add("pageup") + @kb.add("pageup", eager=True) ### eager neglect any other keybinding def _go_up(event) -> None: app = get_app() self.navbar.go_up() app.layout.focus(self.navbar) - @kb.add("pagedown") + @kb.add("pagedown", eager=True) def _go_up(event) -> None: app = get_app() self.navbar.go_down() app.layout.focus(self.navbar) - - - - @kb.add("enter") - def _(event): - pass - return kb def __pt_container__(self): From 963fd934acf783563adb969a2db5cedabd515354 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Tue, 30 Aug 2022 14:18:50 -0700 Subject: [PATCH 087/364] feat:jans-cli add time selection and save --- .../wui_components/jans_data_picker.py | 323 ++++++++++++------ 1 file changed, 227 insertions(+), 96 deletions(-) diff --git a/jans-cli-tui/wui_components/jans_data_picker.py b/jans-cli-tui/wui_components/jans_data_picker.py index 40f0135c7b8..b54ddfda04c 100644 --- a/jans-cli-tui/wui_components/jans_data_picker.py +++ b/jans-cli-tui/wui_components/jans_data_picker.py @@ -29,22 +29,38 @@ #### not finished yet >> data import datetime + class JansSelectDate: - def __init__(self, date='',months=[]): - + def __init__(self, date='',months=[],mytime=[]): + self.hours , self.minutes , self.seconds = mytime + self.change_date = True self.date = date #"11/27/2023" self.months = months self.cord_y = 0 self.cord_x = 0 + + self.extract_date(self.date) #self.depug=Label(text="entries = "+str(self.entries[self.cord_y][self.cord_x])+':',) - self.month_label = Label(text=self.months[self.current_month-1],width=len(self.months[self.current_month-1])) + # -----------------------------------------------------------------------------------------------# + # ------------------------------------ handle month names ---------------------------------------# + # -----------------------------------------------------------------------------------------------# + if len(self.months[self.current_month-1] ) < 9 : ### 9 == the tallest month letters + mon_text = self.months[self.current_month-1] + ' '* (9-len(self.months[self.current_month-1] )) + else : + mon_text = self.months[self.current_month-1] + # -----------------------------------------------------------------------------------------------# + # -----------------------------------------------------------------------------------------------# + # -----------------------------------------------------------------------------------------------# + + self.month_label = Label(text=mon_text,width=len(mon_text)) self.year_label = Label(text=str(self.current_year),width=len(str(self.current_year))) - + + self.container =HSplit(children=[ VSplit([ Button(text='<',left_symbol='',right_symbol='',width=1), @@ -54,25 +70,91 @@ def __init__(self, date='',months=[]): Button(text='<',left_symbol='',right_symbol='',width=1), DynamicContainer(lambda: self.year_label), Button(text='>',left_symbol='',right_symbol='',width=1 ) - ],style="bg:#4D4D4D",padding=1), + ],style="bg:#1e51fa",padding=1), #DynamicContainer(lambda: self.depug), - Window( + Window( content=FormattedTextControl( - text=self._get_formatted_text, - focusable=True, + text=self._get_calender_text, + + ), height=5, cursorline=False, # width=D(), #15, - style="bg:#4D4D4D", + style="bg:#ffffff", + right_margins=[ScrollbarMargin(display_arrows=True),], + wrap_lines=True, + + ), + Window(height=1,char=' ',style="bg:#ffffff"), + Window( + content=FormattedTextControl( + text=self._get_time_text, + focusable=True, + ), + height=2, + cursorline=False, + # width=D(), #15, + style="bg:#bab1b1", right_margins=[ScrollbarMargin(display_arrows=True),], wrap_lines=True - ), + ), ]) - # def set_value(self, value): - # self.value = self.date + + def digitalize (self,number) : + if len(str(number)) == 1 : + return '0'+ str(number) + else : + return str(number) + + def _get_time_text(self): + + result = [] + time_line = [] + + hours = self.digitalize(self.hours) + minutes = self.digitalize(self.minutes) + seconds = self.digitalize(self.seconds) + + hours_list = [hours,minutes,seconds] + + time_line.append(HTML(' H : M : S ')) + time_line.append("\n") + + for i, time_element in enumerate(hours_list): + if i == self.cord_x: + if self.change_date == False: + if i >= 2: + time_line.append(HTML(''.format(self.adjust_sizes(time_element)))) + + elif i == 0 : + time_line.append(HTML(' '.format(self.adjust_sizes(time_element)))) + + else : + time_line.append(HTML(''.format(self.adjust_sizes(time_element)))) + + + else : + if i >= 2: + time_line.append(HTML('{}'.format(self.adjust_sizes(time_element)))) + elif i == 0 : + time_line.append(HTML(' {}:'.format(self.adjust_sizes(time_element)))) + else : + time_line.append(HTML('{}:'.format(self.adjust_sizes(time_element)))) + else : + if i >=2 : + time_line.append(HTML('{}'.format(self.adjust_sizes(time_element)))) + elif i == 0 : + time_line.append(HTML(' {}:'.format(self.adjust_sizes(time_element)))) + else : + time_line.append(HTML('{}:'.format(self.adjust_sizes(time_element)))) + + result= (time_line) + + + return merge_formatted_text(result) def extract_date(self,date): #"11/27/2023" ### this function is for the init date >> passed date from `data` @@ -106,41 +188,56 @@ def adjust_sizes(self,day): else: return ' ' - def _get_formatted_text(self): + def _get_calender_text(self): result = [] week_line = [] for i, week in enumerate(self.entries): for day in range(len(week)) : if i == self.cord_y: if day == self.cord_x: - week_line.append(HTML(''.format(self.adjust_sizes(week[day])))) - + if self.change_date == True: #{} + week_line.append(HTML(''.format(self.adjust_sizes(week[day])))) + else : + week_line.append(HTML('{}'.format(self.adjust_sizes(week[day])))) # result.append(HTML(''.format( day))) else : - week_line.append(HTML('{}'.format(self.adjust_sizes(week[day])))) + week_line.append(HTML('{}'.format(self.adjust_sizes(week[day])))) else: - week_line.append(HTML('{}'.format(self.adjust_sizes(week[day])))) + week_line.append(HTML('{}'.format(self.adjust_sizes(week[day])))) result= (week_line) result.append("\n") return merge_formatted_text(result) + def inc_month(self,day): - if self.current_month != 12: - self.current_month+=1 - self.month_label = Label(text=self.months[self.current_month-1],width=len(self.months[self.current_month-1])) - current_date = str(self.current_month)+'/'+str(day) + '/'+str(self.current_year) - self.extract_date(current_date) - + if self.change_date == True : + if self.current_month != 12: + self.current_month+=1 + if len(self.months[self.current_month-1] ) < 9 : + mon_text = self.months[self.current_month-1] + ' '* (9-len(self.months[self.current_month-1] )) + else : + mon_text = self.months[self.current_month-1] + self.month_label = Label(text=mon_text,width=len(mon_text)) + current_date = str(self.current_month)+'/'+str(day) + '/'+str(self.current_year) + self.extract_date(current_date) + def dec_month(self,day): - if self.current_month > 1: - self.current_month-=1 - self.month_label = Label(text=self.months[self.current_month-1],width=len(self.months[self.current_month-1])) - current_date = str(self.current_month)+'/'+str(day) + '/'+str(self.current_year) - self.extract_date(current_date) - + if self.change_date == True : + if self.current_month > 1: + self.current_month-=1 + if len(self.months[self.current_month-1] ) < 9 : + mon_text = self.months[self.current_month-1] + ' '* (9-len(self.months[self.current_month-1] )) + else : + mon_text = self.months[self.current_month-1] + + + self.month_label = Label(text=mon_text,width=len(mon_text)) + current_date = str(self.current_month)+'/'+str(day) + '/'+str(self.current_year) + self.extract_date(current_date) + def inc_year(self,day): self.current_year+=1 @@ -156,33 +253,80 @@ def dec_year(self,day): self.extract_date(current_date)# 20/2/1997 def up(self): - if self.cord_y == 0 or int(self.entries[self.cord_y-1][self.cord_x]) == 0: - self.dec_month(day=1) + if self.change_date == True : + if self.cord_y == 0 or int(self.entries[self.cord_y-1][self.cord_x]) == 0: + self.dec_month(day=1) + else: + self.cord_y = (self.cord_y - 1)# % 5 + #self.depug=Label(text="entries = "+str(self.entries[self.cord_y][self.cord_x])+':',) else: - self.cord_y = (self.cord_y - 1)# % 5 - #self.depug=Label(text="entries = "+str(self.entries[self.cord_y][self.cord_x])+':',) + if self.cord_x ==0 : + self.hours +=1 + elif self.cord_x ==1 : + self.minutes +=1 + elif self.cord_x ==2 : + self.seconds +=1 + + if self.hours >= 24 : + self.hours = 0 + if self.minutes >= 60 : + self.minutes = 0 + if self.seconds >= 60 : + self.seconds = 0 def down(self): - - if self.cord_y == 4 or int(self.entries[self.cord_y+1][self.cord_x]) == 0: - self.inc_month(day=28) - else: - self.cord_y = (self.cord_y + 1)# % 5 + if self.change_date == True : + if self.cord_y == 4 or int(self.entries[self.cord_y+1][self.cord_x]) == 0: + self.inc_month(day=28) + else: + self.cord_y = (self.cord_y + 1)# % 5 #self.depug=Label(text="entries = "+str(self.entries[self.cord_y][self.cord_x])+':',) + else: + if self.cord_x ==0 : + self.hours -=1 + elif self.cord_x ==1 : + self.minutes -=1 + elif self.cord_x ==2 : + self.seconds -=1 + + if self.hours <=0 : + self.hours = 23 + if self.minutes <=0 : + self.minutes = 59 + if self.seconds <=0 : + self.seconds = 59 def right(self): - if self.cord_x == 6 or int(self.entries[self.cord_y][self.cord_x+1]) == 0: - self.inc_year(day=7) - else : - self.cord_x = (self.cord_x + 1) #% 7 - #self.depug=Label(text="entries = "+str(self.entries[self.cord_y][self.cord_x])+':',) + if self.change_date == True : + if self.cord_x == 6 or int(self.entries[self.cord_y][self.cord_x+1]) == 0: + self.inc_year(day=7) + else : + self.cord_x = (self.cord_x + 1) #% 7 + + else: + if self.cord_x >= 2 : + pass + else : + self.cord_x = (self.cord_x + 1) #% 7 def left(self): - if self.cord_x == 0 or int(self.entries[self.cord_y][self.cord_x-1]) == 0: - self.dec_year(day=1) + if self.change_date == True : + if self.cord_x == 0 or int(self.entries[self.cord_y][self.cord_x-1]) == 0: + self.dec_year(day=1) + else: + self.cord_x = (self.cord_x - 1)# % 7 + self.depug=Label(text="cord_y = "+str(self.cord_y)+':',) else: - self.cord_x = (self.cord_x - 1)# % 7 - #self.depug=Label(text="entries = "+str(self.entries[self.cord_y][self.cord_x])+':',) + if self.cord_x <=0 : + pass + else : + self.cord_x = (self.cord_x - 1) #% 7 + #self.depug=Label(text="cord_y = "+str(self.cord_y)+':',) + + def next(self): + self.change_date = not self.change_date + if self.change_date == False : + self.cord_x = 0 def __pt_container__(self): @@ -220,7 +364,7 @@ def __init__(self, value): # ex: value = "2023-11-27T14:05:35" key_bindings=self._get_key_bindings(), ), height= 10) #D()) #5 ## large sized enties get >> (window too small) - self.select_box = JansSelectDate(date=self.date,months=self.months ) + self.select_box = JansSelectDate(date=self.date,months=self.months,mytime=[self.houres,self.minutes,self.seconds] ) self.select_box_float = Float(content=self.select_box, xcursor=True, ycursor=True) @property @@ -252,7 +396,8 @@ def _enter(event) -> None: years = int(self.select_box.current_year) months =int(self.select_box.current_month) days = int(self.select_box.entries[self.select_box.cord_y][self.select_box.cord_x] ) - t = (years, months,days,self.houres,self.minutes,self.seconds,0,0,0) ## the up increment + + t = (years, months,days,(self.select_box.hours-8),self.select_box.minutes,self.select_box.seconds,0,0,0) ## the up increment t = time.mktime(t) self.text= (time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(t))) get_app().layout.container.floats.remove(self.select_box_float) @@ -329,63 +474,49 @@ def _up(event): else : self.select_box.left() - @kb.add("+") def _up(event): - ts = time.strptime(self.text[:19], "%Y-%m-%dT%H:%M:%S") # "2023-11-27" - years =int(time.strftime("%Y",ts)) - months = int(time.strftime("%m",ts)) - days = int(time.strftime("%d",ts)) - self.houres = int(time.strftime("%H",ts)) -8 ## it start from 0 to 23 - self.minutes =int(time.strftime("%M",ts)) - self.seconds = int(time.strftime("%S",ts)) +1 - - t = (years, months,days,self.houres,self.minutes,self.seconds,0,0,0) ## the up increment - t = time.mktime(t) - self.text= (time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(t))) + if self.select_box_float not in get_app().layout.container.floats: + ts = time.strptime(self.text[:19], "%Y-%m-%dT%H:%M:%S") # "2023-11-27" + years =int(time.strftime("%Y",ts)) + months = int(time.strftime("%m",ts)) + days = int(time.strftime("%d",ts)) + self.houres = int(time.strftime("%H",ts)) -8 ## it start from 0 to 23 + self.minutes =int(time.strftime("%M",ts)) + self.seconds = int(time.strftime("%S",ts)) +1 + + t = (years, months,days,self.houres,self.minutes,self.seconds,0,0,0) ## the up increment + t = time.mktime(t) + self.text= (time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(t))) @kb.add("-") def _up(event): - ts = time.strptime(self.text[:19], "%Y-%m-%dT%H:%M:%S") # "2023-11-27" - years =int(time.strftime("%Y",ts)) - months = int(time.strftime("%m",ts)) - days = int(time.strftime("%d",ts)) - self.houres = int(time.strftime("%H",ts)) -8 ## it start from 0 to 23 - self.minutes =int(time.strftime("%M",ts)) - self.seconds = int(time.strftime("%S",ts)) -1 - - t = (years, months,days,self.houres,self.minutes,self.seconds,0,0,0) ## the up increment - t = time.mktime(t) - self.text= (time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(t))) - - - - - - @kb.add("w") - def _up(event): - self.select_box.inc_year() - - @kb.add("s") - def _up(event): - self.select_box.dec_year() - - @kb.add("a") - def _up(event): - self.select_box.inc_month() - - @kb.add("d") - def _up(event): - self.select_box.dec_month() - + if self.select_box_float not in get_app().layout.container.floats: + ts = time.strptime(self.text[:19], "%Y-%m-%dT%H:%M:%S") # "2023-11-27" + years =int(time.strftime("%Y",ts)) + months = int(time.strftime("%m",ts)) + days = int(time.strftime("%d",ts)) + self.houres = int(time.strftime("%H",ts)) -8 ## it start from 0 to 23 + self.minutes =int(time.strftime("%M",ts)) + self.seconds = int(time.strftime("%S",ts)) -1 + + t = (years, months,days,self.houres,self.minutes,self.seconds,0,0,0) ## the up increment + t = time.mktime(t) + self.text= (time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(t))) - @kb.add("escape") @kb.add("tab") + def _(event): + if self.select_box_float in get_app().layout.container.floats: + self.select_box.next() + else : + _focus_next(event) + + @kb.add("escape") def _(event): if self.select_box_float in get_app().layout.container.floats: get_app().layout.container.floats.remove(self.select_box_float) - - _focus_next(event) + _focus_next(event) + return kb From 2ba687787f32504d7b695ef248a964938df31672 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Wed, 31 Aug 2022 12:13:07 +0300 Subject: [PATCH 088/364] fix: jans-cli date picler styling --- .../wui_components/jans_data_picker.py | 35 +++++++++---------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/jans-cli-tui/wui_components/jans_data_picker.py b/jans-cli-tui/wui_components/jans_data_picker.py index b54ddfda04c..551d64cf4ff 100644 --- a/jans-cli-tui/wui_components/jans_data_picker.py +++ b/jans-cli-tui/wui_components/jans_data_picker.py @@ -72,22 +72,19 @@ def __init__(self, date='',months=[],mytime=[]): Button(text='>',left_symbol='',right_symbol='',width=1 ) ],style="bg:#1e51fa",padding=1), #DynamicContainer(lambda: self.depug), - + Window( content=FormattedTextControl( text=self._get_calender_text, - - ), height=5, cursorline=False, # width=D(), #15, - style="bg:#ffffff", + style="bg:#D3D3D3", right_margins=[ScrollbarMargin(display_arrows=True),], wrap_lines=True, - ), - Window(height=1,char=' ',style="bg:#ffffff"), + ), Window( content=FormattedTextControl( text=self._get_time_text, @@ -99,7 +96,7 @@ def __init__(self, date='',months=[],mytime=[]): style="bg:#bab1b1", right_margins=[ScrollbarMargin(display_arrows=True),], wrap_lines=True - ), + ), ]) @@ -127,13 +124,13 @@ def _get_time_text(self): if i == self.cord_x: if self.change_date == False: if i >= 2: - time_line.append(HTML(''.format(self.adjust_sizes(time_element)))) + time_line.append(HTML(''.format(self.adjust_sizes(time_element)))) elif i == 0 : - time_line.append(HTML(' '.format(self.adjust_sizes(time_element)))) + time_line.append(HTML(' '.format(self.adjust_sizes(time_element)))) else : - time_line.append(HTML(''.format(self.adjust_sizes(time_element)))) + time_line.append(HTML(''.format(self.adjust_sizes(time_element)))) else : @@ -196,7 +193,7 @@ def _get_calender_text(self): if i == self.cord_y: if day == self.cord_x: if self.change_date == True: #{} - week_line.append(HTML(''.format(self.adjust_sizes(week[day])))) + week_line.append(HTML(''.format(self.adjust_sizes(week[day])))) else : week_line.append(HTML('{}'.format(self.adjust_sizes(week[day])))) # result.append(HTML(''.format( day))) @@ -335,8 +332,8 @@ def __pt_container__(self): class DateSelectWidget: def __init__(self, value): # ex: value = "2023-11-27T14:05:35" - self.months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] - # text >> showed in the widget + self.months = [calendar.month_name[i] for i in range(1,13)] + # text >> showed in the widget if value: self.text = value @@ -350,12 +347,12 @@ def __init__(self, value): # ex: value = "2023-11-27T14:05:35" self.date = str(today.month) +'/' +str(today.day) +'/'+str(today.year) ## '11/27/2023' ## self.text = "Enter to Select" now = datetime.datetime.now() - self.houres = int(now.strftime("%H") ) + self.houres = int(now.strftime("%H")) self.minutes =int(now.strftime("%M")) self.seconds = int(now.strftime("%S")) - self.value = str(value) - + self.value = str(value) + self.dropdown = True self.window = Window( content=FormattedTextControl( @@ -499,9 +496,9 @@ def _up(event): self.houres = int(time.strftime("%H",ts)) -8 ## it start from 0 to 23 self.minutes =int(time.strftime("%M",ts)) self.seconds = int(time.strftime("%S",ts)) -1 - + t = (years, months,days,self.houres,self.minutes,self.seconds,0,0,0) ## the up increment - t = time.mktime(t) + t = time.mktime(t) self.text= (time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(t))) @kb.add("tab") @@ -509,7 +506,7 @@ def _(event): if self.select_box_float in get_app().layout.container.floats: self.select_box.next() else : - _focus_next(event) + _focus_next(event) @kb.add("escape") def _(event): From 5148ca0028c7a8621a80c444e0600592ed9dc771 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Wed, 31 Aug 2022 13:15:11 +0300 Subject: [PATCH 089/364] refactor: jans-cli code reuse --- .../wui_components/jans_data_picker.py | 278 ++++++------------ 1 file changed, 94 insertions(+), 184 deletions(-) diff --git a/jans-cli-tui/wui_components/jans_data_picker.py b/jans-cli-tui/wui_components/jans_data_picker.py index 551d64cf4ff..32281ff4fd1 100644 --- a/jans-cli-tui/wui_components/jans_data_picker.py +++ b/jans-cli-tui/wui_components/jans_data_picker.py @@ -33,15 +33,13 @@ class JansSelectDate: def __init__(self, date='',months=[],mytime=[]): - self.hours , self.minutes , self.seconds = mytime + self.hours , self.minuts , self.seconds = mytime self.change_date = True self.date = date #"11/27/2023" self.months = months self.cord_y = 0 self.cord_x = 0 - - self.extract_date(self.date) @@ -112,41 +110,26 @@ def _get_time_text(self): time_line = [] hours = self.digitalize(self.hours) - minutes = self.digitalize(self.minutes) + minuts = self.digitalize(self.minuts) seconds = self.digitalize(self.seconds) - hours_list = [hours,minutes,seconds] + hours_list = [hours,minuts,seconds] time_line.append(HTML(' H : M : S ')) time_line.append("\n") - for i, time_element in enumerate(hours_list): - if i == self.cord_x: - if self.change_date == False: - if i >= 2: - time_line.append(HTML(''.format(self.adjust_sizes(time_element)))) - - elif i == 0 : - time_line.append(HTML(' '.format(self.adjust_sizes(time_element)))) - - else : - time_line.append(HTML(''.format(self.adjust_sizes(time_element)))) - - - else : - if i >= 2: - time_line.append(HTML('{}'.format(self.adjust_sizes(time_element)))) - elif i == 0 : - time_line.append(HTML(' {}:'.format(self.adjust_sizes(time_element)))) - else : - time_line.append(HTML('{}:'.format(self.adjust_sizes(time_element)))) + for i, time_element in enumerate(hours_list): + if i >= 2: + space_, colon_ = 0, '' + elif i == 0 : + space_, colon_ = 7, ':' + else: + space_, colon_ = 0, ':' + + if i == self.cord_x and self.change_date == False: + time_line.append(HTML('{}'.format(' '*space_, self.adjust_sizes(time_element), colon_))) else : - if i >=2 : - time_line.append(HTML('{}'.format(self.adjust_sizes(time_element)))) - elif i == 0 : - time_line.append(HTML(' {}:'.format(self.adjust_sizes(time_element)))) - else : - time_line.append(HTML('{}:'.format(self.adjust_sizes(time_element)))) + time_line.append(HTML('{}{}{}'.format(' '*space_, self.adjust_sizes(time_element), colon_))) result= (time_line) @@ -189,66 +172,68 @@ def _get_calender_text(self): result = [] week_line = [] for i, week in enumerate(self.entries): - for day in range(len(week)) : - if i == self.cord_y: - if day == self.cord_x: - if self.change_date == True: #{} - week_line.append(HTML(''.format(self.adjust_sizes(week[day])))) - else : - week_line.append(HTML('{}'.format(self.adjust_sizes(week[day])))) - # result.append(HTML(''.format( day))) - else : - week_line.append(HTML('{}'.format(self.adjust_sizes(week[day])))) + for day in range(len(week)): + if i == self.cord_y and day == self.cord_x and self.change_date == True: + week_line.append(HTML(''.format(self.adjust_sizes(week[day])))) else: - week_line.append(HTML('{}'.format(self.adjust_sizes(week[day])))) - + week_line.append(HTML('{}'.format(self.adjust_sizes(week[day])))) + result= (week_line) result.append("\n") return merge_formatted_text(result) - - def inc_month(self,day): - if self.change_date == True : - if self.current_month != 12: - self.current_month+=1 - if len(self.months[self.current_month-1] ) < 9 : - mon_text = self.months[self.current_month-1] + ' '* (9-len(self.months[self.current_month-1] )) - else : - mon_text = self.months[self.current_month-1] - self.month_label = Label(text=mon_text,width=len(mon_text)) - current_date = str(self.current_month)+'/'+str(day) + '/'+str(self.current_year) - self.extract_date(current_date) - - def dec_month(self,day): + + def adjust_month(self, day, i): if self.change_date == True : - if self.current_month > 1: - self.current_month-=1 - if len(self.months[self.current_month-1] ) < 9 : - mon_text = self.months[self.current_month-1] + ' '* (9-len(self.months[self.current_month-1] )) - else : - mon_text = self.months[self.current_month-1] + if i == 1 and self.current_month == 12: + return + if i == -1 and self.current_month == 1: + return + + self.current_month += i + if len(self.months[self.current_month-1] ) < 9 : + mon_text = self.months[self.current_month-1] + ' '* (9-len(self.months[self.current_month-1] )) + else : + mon_text = self.months[self.current_month-1] + self.month_label = Label(text=mon_text,width=len(mon_text)) + current_date = str(self.current_month)+'/'+str(day) + '/'+str(self.current_year) + self.extract_date(current_date) - self.month_label = Label(text=mon_text,width=len(mon_text)) - current_date = str(self.current_month)+'/'+str(day) + '/'+str(self.current_year) - self.extract_date(current_date) - - def inc_year(self,day): - - self.current_year+=1 - self.year_label = Label(text=str(self.current_year),width=len(str(self.current_year))) - current_date = str(day)+'/'+str(self.current_month) + '/'+str(self.current_year) - self.extract_date(current_date)# 20/2/1997 + def inc_month(self,day): + self.adjust_month(day, 1) - def dec_year(self,day): - - self.current_year-=1 + def dec_month(self,day): + self.adjust_month(day, -1) + + def adjust_year(self, day, i): + self.current_year += i self.year_label = Label(text=str(self.current_year),width=len(str(self.current_year))) current_date = str(day)+'/'+str(self.current_month) + '/'+str(self.current_year) self.extract_date(current_date)# 20/2/1997 + + def inc_year(self, day): + self.adjust_year(day, 1) + + def dec_year(self, day): + self.adjust_year(day, -1) + + + def adjust_time(self, i): + if self.cord_x ==0 : + self.hours +=i + elif self.cord_x ==1 : + self.minuts +=i + elif self.cord_x ==2 : + self.seconds +=i + + self.hours = abs(self.hours % 24) + self.minuts = abs(self.minuts % 60) + self.seconds = abs(self.seconds % 60) + def up(self): if self.change_date == True : if self.cord_y == 0 or int(self.entries[self.cord_y-1][self.cord_x]) == 0: @@ -257,19 +242,7 @@ def up(self): self.cord_y = (self.cord_y - 1)# % 5 #self.depug=Label(text="entries = "+str(self.entries[self.cord_y][self.cord_x])+':',) else: - if self.cord_x ==0 : - self.hours +=1 - elif self.cord_x ==1 : - self.minutes +=1 - elif self.cord_x ==2 : - self.seconds +=1 - - if self.hours >= 24 : - self.hours = 0 - if self.minutes >= 60 : - self.minutes = 0 - if self.seconds >= 60 : - self.seconds = 0 + self.adjust_time(1) def down(self): if self.change_date == True : @@ -279,19 +252,8 @@ def down(self): self.cord_y = (self.cord_y + 1)# % 5 #self.depug=Label(text="entries = "+str(self.entries[self.cord_y][self.cord_x])+':',) else: - if self.cord_x ==0 : - self.hours -=1 - elif self.cord_x ==1 : - self.minutes -=1 - elif self.cord_x ==2 : - self.seconds -=1 - - if self.hours <=0 : - self.hours = 23 - if self.minutes <=0 : - self.minutes = 59 - if self.seconds <=0 : - self.seconds = 59 + self.adjust_time(-1) + def right(self): if self.change_date == True : @@ -302,7 +264,7 @@ def right(self): else: if self.cord_x >= 2 : - pass + pass else : self.cord_x = (self.cord_x + 1) #% 7 @@ -330,7 +292,7 @@ def __pt_container__(self): return self.container -class DateSelectWidget: +class DateSelectWidget: def __init__(self, value): # ex: value = "2023-11-27T14:05:35" self.months = [calendar.month_name[i] for i in range(1,13)] # text >> showed in the widget @@ -339,16 +301,16 @@ def __init__(self, value): # ex: value = "2023-11-27T14:05:35" self.text = value ts = time.strptime(value[:19], "%Y-%m-%dT%H:%M:%S") # "2023-11-27" self.date = time.strftime("%m/%d/%Y", ts) # "11/27/2023" - self.houres = int(time.strftime("%H",ts)) - self.minutes =int(time.strftime("%M",ts)) + self.hours = int(time.strftime("%H",ts)) + self.minuts =int(time.strftime("%M",ts)) self.seconds = int(time.strftime("%S",ts)) else: today = datetime.date.today() self.date = str(today.month) +'/' +str(today.day) +'/'+str(today.year) ## '11/27/2023' ## self.text = "Enter to Select" now = datetime.datetime.now() - self.houres = int(now.strftime("%H")) - self.minutes =int(now.strftime("%M")) + self.hours = int(now.strftime("%H")) + self.minuts =int(now.strftime("%M")) self.seconds = int(now.strftime("%S")) self.value = str(value) @@ -361,7 +323,7 @@ def __init__(self, value): # ex: value = "2023-11-27T14:05:35" key_bindings=self._get_key_bindings(), ), height= 10) #D()) #5 ## large sized enties get >> (window too small) - self.select_box = JansSelectDate(date=self.date,months=self.months,mytime=[self.houres,self.minutes,self.seconds] ) + self.select_box = JansSelectDate(date=self.date,months=self.months,mytime=[self.hours,self.minuts,self.seconds] ) self.select_box_float = Float(content=self.select_box, xcursor=True, ycursor=True) @property @@ -373,14 +335,27 @@ def value(self): def value(self, value): #passed_value = self.value self._value = self.value - + + def make_time(self, text): + ts = time.strptime(text[:19], "%Y-%m-%dT%H:%M:%S") # "2023-11-27" + years =int(time.strftime("%Y",ts)) + months = int(time.strftime("%m",ts)) + days = int(time.strftime("%d",ts)) + self.hours = int(time.strftime("%H",ts)) -7 ## it start from 0 to 23 + self.minuts =int(time.strftime("%M",ts)) + self.seconds = int(time.strftime("%S",ts)) + + t = (years, months,days,self.hours,self.minuts,self.seconds,0,0,0) ## the up increment + t = time.mktime(t) + self.text= (time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(t))) + def _get_text(self): if get_app().layout.current_window is self.window: return HTML('> <'.format('#00FF00', self.text)) return '> {} <'.format(self.text) def _get_key_bindings(self): - kb = KeyBindings() + kb = KeyBindings() def _focus_next(event): focus_next(event) @@ -394,7 +369,7 @@ def _enter(event) -> None: months =int(self.select_box.current_month) days = int(self.select_box.entries[self.select_box.cord_y][self.select_box.cord_x] ) - t = (years, months,days,(self.select_box.hours-8),self.select_box.minutes,self.select_box.seconds,0,0,0) ## the up increment + t = (years, months,days,(self.select_box.hours-8),self.select_box.minuts,self.select_box.seconds,0,0,0) ## the up increment t = time.mktime(t) self.text= (time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(t))) get_app().layout.container.floats.remove(self.select_box_float) @@ -402,104 +377,40 @@ def _enter(event) -> None: @kb.add("up") def _up(event): if self.select_box_float not in get_app().layout.container.floats: - ts = time.strptime(self.text[:19], "%Y-%m-%dT%H:%M:%S") # "2023-11-27" - years =int(time.strftime("%Y",ts)) - months = int(time.strftime("%m",ts)) - days = int(time.strftime("%d",ts)) - self.houres = int(time.strftime("%H",ts)) -7 ## it start from 0 to 23 - self.minutes =int(time.strftime("%M",ts)) - self.seconds = int(time.strftime("%S",ts)) - - t = (years, months,days,self.houres,self.minutes,self.seconds,0,0,0) ## the up increment - t = time.mktime(t) - self.text= (time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(t))) - + self.make_time(self.text) else : self.select_box.up() - @kb.add("down") def _up(event): if self.select_box_float not in get_app().layout.container.floats: - ts = time.strptime(self.text[:19], "%Y-%m-%dT%H:%M:%S") # "2023-11-27" - years =int(time.strftime("%Y",ts)) - months = int(time.strftime("%m",ts)) - days = int(time.strftime("%d",ts)) - self.houres = int(time.strftime("%H",ts))-9 ## it start from 0 to 23 - self.minutes =int(time.strftime("%M",ts)) - self.seconds = int(time.strftime("%S",ts)) - t = (years, months,days,self.houres,self.minutes,self.seconds,0,0,0) ## the down increment - t = time.mktime(t) - self.text= (time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(t))) - + self.make_time(self.text) else : self.select_box.down() @kb.add("right") def _up(event): if self.select_box_float not in get_app().layout.container.floats: - ts = time.strptime(self.text[:19], "%Y-%m-%dT%H:%M:%S") # "2023-11-27" - years =int(time.strftime("%Y",ts)) - months = int(time.strftime("%m",ts)) - days = int(time.strftime("%d",ts)) - self.houres = int(time.strftime("%H",ts)) -8 ## it start from 0 to 23 - self.minutes =int(time.strftime("%M",ts)) -1 - self.seconds = int(time.strftime("%S",ts)) - - t = (years, months,days,self.houres,self.minutes,self.seconds,0,0,0) ## the up increment - t = time.mktime(t) - self.text= (time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(t))) - + self.make_time(self.text) else : self.select_box.right() @kb.add("left") def _up(event): if self.select_box_float not in get_app().layout.container.floats: - ts = time.strptime(self.text[:19], "%Y-%m-%dT%H:%M:%S") # "2023-11-27" - years =int(time.strftime("%Y",ts)) - months = int(time.strftime("%m",ts)) - days = int(time.strftime("%d",ts)) - self.houres = int(time.strftime("%H",ts)) -8 ## it start from 0 to 23 - self.minutes =int(time.strftime("%M",ts)) +1 - self.seconds = int(time.strftime("%S",ts)) - - t = (years, months,days,self.houres,self.minutes,self.seconds,0,0,0) ## the up increment - t = time.mktime(t) - self.text= (time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(t))) - + self.make_time(self.text) else : self.select_box.left() @kb.add("+") def _up(event): if self.select_box_float not in get_app().layout.container.floats: - ts = time.strptime(self.text[:19], "%Y-%m-%dT%H:%M:%S") # "2023-11-27" - years =int(time.strftime("%Y",ts)) - months = int(time.strftime("%m",ts)) - days = int(time.strftime("%d",ts)) - self.houres = int(time.strftime("%H",ts)) -8 ## it start from 0 to 23 - self.minutes =int(time.strftime("%M",ts)) - self.seconds = int(time.strftime("%S",ts)) +1 - - t = (years, months,days,self.houres,self.minutes,self.seconds,0,0,0) ## the up increment - t = time.mktime(t) - self.text= (time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(t))) + self.make_time(self.text) @kb.add("-") def _up(event): if self.select_box_float not in get_app().layout.container.floats: - ts = time.strptime(self.text[:19], "%Y-%m-%dT%H:%M:%S") # "2023-11-27" - years =int(time.strftime("%Y",ts)) - months = int(time.strftime("%m",ts)) - days = int(time.strftime("%d",ts)) - self.houres = int(time.strftime("%H",ts)) -8 ## it start from 0 to 23 - self.minutes =int(time.strftime("%M",ts)) - self.seconds = int(time.strftime("%S",ts)) -1 - - t = (years, months,days,self.houres,self.minutes,self.seconds,0,0,0) ## the up increment - t = time.mktime(t) - self.text= (time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(t))) + self.make_time(self.text) @kb.add("tab") def _(event): @@ -507,13 +418,12 @@ def _(event): self.select_box.next() else : _focus_next(event) - + @kb.add("escape") def _(event): if self.select_box_float in get_app().layout.container.floats: get_app().layout.container.floats.remove(self.select_box_float) _focus_next(event) - return kb From 1082b77b5cf1dafab462acd8e7ac23f861e593fb Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Wed, 31 Aug 2022 13:57:21 +0300 Subject: [PATCH 090/364] fix: jans-cli make selected date bold when navigate to hour adjustment --- .../wui_components/jans_data_picker.py | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/jans-cli-tui/wui_components/jans_data_picker.py b/jans-cli-tui/wui_components/jans_data_picker.py index 32281ff4fd1..d8c2c4a380b 100644 --- a/jans-cli-tui/wui_components/jans_data_picker.py +++ b/jans-cli-tui/wui_components/jans_data_picker.py @@ -37,10 +37,9 @@ def __init__(self, date='',months=[],mytime=[]): self.change_date = True self.date = date #"11/27/2023" self.months = months - self.cord_y = 0 self.cord_x = 0 - + self.selected_cord = (0, 0) self.extract_date(self.date) #self.depug=Label(text="entries = "+str(self.entries[self.cord_y][self.cord_x])+':',) @@ -126,7 +125,7 @@ def _get_time_text(self): else: space_, colon_ = 0, ':' - if i == self.cord_x and self.change_date == False: + if i == self.cord_x and not self.change_date: time_line.append(HTML('{}'.format(' '*space_, self.adjust_sizes(time_element), colon_))) else : time_line.append(HTML('{}{}{}'.format(' '*space_, self.adjust_sizes(time_element), colon_))) @@ -136,14 +135,14 @@ def _get_time_text(self): return merge_formatted_text(result) - def extract_date(self,date): #"11/27/2023" + def extract_date(self, date): #"11/27/2023" ### this function is for the init date >> passed date from `data` day = int(date.split('/')[1]) self.current_month = int(date.split('/')[0]) self.current_year = int(date.split('/')[-1]) - month_week_list = calendar.monthcalendar(int(self.current_year),int(self.current_month)) + month_week_list = calendar.monthcalendar(int(self.current_year), int(self.current_month)) self.entries = month_week_list @@ -158,6 +157,7 @@ def extract_date(self,date): #"11/27/2023" week_index = self.entries.index(dum_week) self.cord_y = week_index self.cord_x = day_index + self.selected_cord = (self.cord_y, self.cord_x) def adjust_sizes(self,day): if str(day) != '0': @@ -173,8 +173,10 @@ def _get_calender_text(self): week_line = [] for i, week in enumerate(self.entries): for day in range(len(week)): - if i == self.cord_y and day == self.cord_x and self.change_date == True: + if i == self.cord_y and day == self.cord_x and self.change_date: week_line.append(HTML(''.format(self.adjust_sizes(week[day])))) + elif i == self.selected_cord[1] and day == self.selected_cord[0] and not self.change_date: + week_line.append(HTML('{}'.format(self.adjust_sizes(week[day])))) else: week_line.append(HTML('{}'.format(self.adjust_sizes(week[day])))) @@ -186,7 +188,7 @@ def _get_calender_text(self): def adjust_month(self, day, i): - if self.change_date == True : + if self.change_date: if i == 1 and self.current_month == 12: return if i == -1 and self.current_month == 1: @@ -235,46 +237,50 @@ def adjust_time(self, i): self.seconds = abs(self.seconds % 60) def up(self): - if self.change_date == True : + if self.change_date: if self.cord_y == 0 or int(self.entries[self.cord_y-1][self.cord_x]) == 0: self.dec_month(day=1) else: self.cord_y = (self.cord_y - 1)# % 5 #self.depug=Label(text="entries = "+str(self.entries[self.cord_y][self.cord_x])+':',) + self.selected_cord = (self.cord_x, self.cord_y) else: self.adjust_time(1) def down(self): - if self.change_date == True : + if self.change_date: if self.cord_y == 4 or int(self.entries[self.cord_y+1][self.cord_x]) == 0: self.inc_month(day=28) else: self.cord_y = (self.cord_y + 1)# % 5 #self.depug=Label(text="entries = "+str(self.entries[self.cord_y][self.cord_x])+':',) + self.selected_cord = (self.cord_x, self.cord_y) else: self.adjust_time(-1) def right(self): - if self.change_date == True : + if self.change_date: if self.cord_x == 6 or int(self.entries[self.cord_y][self.cord_x+1]) == 0: self.inc_year(day=7) else : self.cord_x = (self.cord_x + 1) #% 7 - + self.selected_cord = (self.cord_x, self.cord_y) else: if self.cord_x >= 2 : - pass + pass else : self.cord_x = (self.cord_x + 1) #% 7 def left(self): - if self.change_date == True : + if self.change_date: if self.cord_x == 0 or int(self.entries[self.cord_y][self.cord_x-1]) == 0: self.dec_year(day=1) else: self.cord_x = (self.cord_x - 1)# % 7 self.depug=Label(text="cord_y = "+str(self.cord_y)+':',) + self.selected_cord = (self.cord_x, self.cord_y) + self.date_changed = True else: if self.cord_x <=0 : pass @@ -284,7 +290,7 @@ def left(self): def next(self): self.change_date = not self.change_date - if self.change_date == False : + if not self.change_date: self.cord_x = 0 From 7880ee3906b7535f551893c8eb71c9b355f8c359 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Wed, 31 Aug 2022 14:15:05 +0300 Subject: [PATCH 091/364] fix: jans-cli close date picler on pageup pagedown --- jans-cli-tui/wui_components/jans_data_picker.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/jans-cli-tui/wui_components/jans_data_picker.py b/jans-cli-tui/wui_components/jans_data_picker.py index d8c2c4a380b..8b674742150 100644 --- a/jans-cli-tui/wui_components/jans_data_picker.py +++ b/jans-cli-tui/wui_components/jans_data_picker.py @@ -388,45 +388,47 @@ def _up(event): self.select_box.up() @kb.add("down") - def _up(event): + def _down(event): if self.select_box_float not in get_app().layout.container.floats: self.make_time(self.text) else : self.select_box.down() @kb.add("right") - def _up(event): + def _right(event): if self.select_box_float not in get_app().layout.container.floats: self.make_time(self.text) else : self.select_box.right() @kb.add("left") - def _up(event): + def _left(event): if self.select_box_float not in get_app().layout.container.floats: self.make_time(self.text) else : self.select_box.left() @kb.add("+") - def _up(event): + def _plus(event): if self.select_box_float not in get_app().layout.container.floats: self.make_time(self.text) @kb.add("-") - def _up(event): + def _minus(event): if self.select_box_float not in get_app().layout.container.floats: self.make_time(self.text) @kb.add("tab") - def _(event): + def _tab(event): if self.select_box_float in get_app().layout.container.floats: self.select_box.next() else : _focus_next(event) @kb.add("escape") - def _(event): + @kb.add("pageup", eager=True) + @kb.add("pagedown", eager=True) + def _escape(event): if self.select_box_float in get_app().layout.container.floats: get_app().layout.container.floats.remove(self.select_box_float) _focus_next(event) From c6188f39695f537f53eda48165ba10aa5f91ca66 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Wed, 31 Aug 2022 23:13:37 +0300 Subject: [PATCH 092/364] fix: jans-cli dialog focus when save client failed --- jans-cli-tui/jans-cli-tui.py | 6 +++--- jans-cli-tui/wui_components/jans_message_dialog.py | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index 39c797d735a..963a64a1bbf 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -511,14 +511,14 @@ def edit_scope_dialog(self, **params): def show_message(self, title, message, buttons=[]): body = HSplit([Label(message)]) dialog = JansMessageDialog(title=title, body=body, buttons=buttons) - - focused_before = self.layout.current_window + focused_before = self.root_layout.floats[-1].content if self.root_layout.floats else self.layout.current_window float_ = Float(content=dialog) self.root_layout.floats.append(float_) dialog.me = float_ dialog.focus_on_exit = focused_before self.layout.focus(dialog) self.press_tab() + def show_again(self): ## nasted dialog Button self.show_message("Again", "Nasted Dialogs",) @@ -541,4 +541,4 @@ def run(): if __name__ == "__main__": - run() \ No newline at end of file + run() diff --git a/jans-cli-tui/wui_components/jans_message_dialog.py b/jans-cli-tui/wui_components/jans_message_dialog.py index 10230d69593..25e6a032680 100644 --- a/jans-cli-tui/wui_components/jans_message_dialog.py +++ b/jans-cli-tui/wui_components/jans_message_dialog.py @@ -22,10 +22,10 @@ def exit_me(result, handler): if self.me in app.root_layout.floats: app.root_layout.floats.remove(self.me) - try: - app.layout.focus(self.focus_on_exit) - except: - pass + #try: + app.layout.focus(self.focus_on_exit) + #except: + # pass blist = [] From f1ef9552a7e282595a16f09763e15c61d61b5a4c Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Wed, 31 Aug 2022 16:34:32 -0700 Subject: [PATCH 093/364] feat:jans-cli add Docstrings --- jans-cli-tui/docs/docs/Gallery/cli.md | 0 jans-cli-tui/docs/docs/Gallery/tui.md | 4 + jans-cli-tui/docs/docs/home/about.md | 1 + jans-cli-tui/docs/docs/home/index.md | 4 + .../docs/models/oauth/edit_client_dialog.md | 1 + .../docs/models/oauth/edit_scope_dialog.md | 1 + jans-cli-tui/docs/docs/models/oauth/oauth.md | 1 + .../docs/wui_components/jans_cli_dialog.md | 1 + .../docs/wui_components/jans_data_picker.md | 1 + .../docs/docs/wui_components/jans_dialog.md | 1 + .../wui_components/jans_dialog_with_nav.md | 1 + .../docs/wui_components/jans_drop_down.md | 1 + .../wui_components/jans_message_dialog.md | 1 + .../docs/docs/wui_components/jans_nav_bar.md | 1 + .../docs/wui_components/jans_side_nav_bar.md | 1 + .../docs/wui_components/jans_vetrical_nav.md | 1 + jans-cli-tui/docs/img/favicon.ico | Bin 0 -> 15406 bytes jans-cli-tui/docs/img/new_tui/new_tui1.png | Bin 0 -> 95761 bytes jans-cli-tui/jans-cli-tui.py | 8 +- jans-cli-tui/mkdocs.yml | 55 ++++++++++++++ .../models/oauth/edit_client_dialog.py | 6 ++ jans-cli-tui/models/oauth/oauth.py | 2 +- jans-cli-tui/preview.ipynb | 48 ++++++++++++ .../wui_components/jans_cli_dialog.py | 14 ++++ .../wui_components/jans_data_picker.py | 44 +++++++++-- jans-cli-tui/wui_components/jans_dialog.py | 2 + .../wui_components/jans_dialog_with_nav.py | 36 ++++++++- jans-cli-tui/wui_components/jans_drop_down.py | 68 +++++++++++++++-- .../wui_components/jans_message_dialog.py | 22 +++++- jans-cli-tui/wui_components/jans_nav_bar.py | 34 ++++++++- .../wui_components/jans_side_nav_bar.py | 71 +++++++++++++----- .../wui_components/jans_vetrical_nav.py | 67 ++++++++++++++--- 32 files changed, 445 insertions(+), 53 deletions(-) create mode 100644 jans-cli-tui/docs/docs/Gallery/cli.md create mode 100644 jans-cli-tui/docs/docs/Gallery/tui.md create mode 100644 jans-cli-tui/docs/docs/home/about.md create mode 100644 jans-cli-tui/docs/docs/home/index.md create mode 100644 jans-cli-tui/docs/docs/models/oauth/edit_client_dialog.md create mode 100644 jans-cli-tui/docs/docs/models/oauth/edit_scope_dialog.md create mode 100644 jans-cli-tui/docs/docs/models/oauth/oauth.md create mode 100644 jans-cli-tui/docs/docs/wui_components/jans_cli_dialog.md create mode 100644 jans-cli-tui/docs/docs/wui_components/jans_data_picker.md create mode 100644 jans-cli-tui/docs/docs/wui_components/jans_dialog.md create mode 100644 jans-cli-tui/docs/docs/wui_components/jans_dialog_with_nav.md create mode 100644 jans-cli-tui/docs/docs/wui_components/jans_drop_down.md create mode 100644 jans-cli-tui/docs/docs/wui_components/jans_message_dialog.md create mode 100644 jans-cli-tui/docs/docs/wui_components/jans_nav_bar.md create mode 100644 jans-cli-tui/docs/docs/wui_components/jans_side_nav_bar.md create mode 100644 jans-cli-tui/docs/docs/wui_components/jans_vetrical_nav.md create mode 100644 jans-cli-tui/docs/img/favicon.ico create mode 100644 jans-cli-tui/docs/img/new_tui/new_tui1.png create mode 100644 jans-cli-tui/mkdocs.yml create mode 100644 jans-cli-tui/preview.ipynb diff --git a/jans-cli-tui/docs/docs/Gallery/cli.md b/jans-cli-tui/docs/docs/Gallery/cli.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/jans-cli-tui/docs/docs/Gallery/tui.md b/jans-cli-tui/docs/docs/Gallery/tui.md new file mode 100644 index 00000000000..9316bbcbd4d --- /dev/null +++ b/jans-cli-tui/docs/docs/Gallery/tui.md @@ -0,0 +1,4 @@ +## Gallarey +demonstrating the possibilities of jans-tui. + +![Screenshot](docs/img/new_tui/new_tui1.PNG) \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/home/about.md b/jans-cli-tui/docs/docs/home/about.md new file mode 100644 index 00000000000..0e87f807786 --- /dev/null +++ b/jans-cli-tui/docs/docs/home/about.md @@ -0,0 +1 @@ +--------- \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/home/index.md b/jans-cli-tui/docs/docs/home/index.md new file mode 100644 index 00000000000..09d1f529cbc --- /dev/null +++ b/jans-cli-tui/docs/docs/home/index.md @@ -0,0 +1,4 @@ + +## Welcome to the Janssen Project +The world's leading community governed digital identity platform and introducing Agama, a programming language for cloud authentication. + diff --git a/jans-cli-tui/docs/docs/models/oauth/edit_client_dialog.md b/jans-cli-tui/docs/docs/models/oauth/edit_client_dialog.md new file mode 100644 index 00000000000..cca2afdb4e2 --- /dev/null +++ b/jans-cli-tui/docs/docs/models/oauth/edit_client_dialog.md @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/models/oauth/edit_scope_dialog.md b/jans-cli-tui/docs/docs/models/oauth/edit_scope_dialog.md new file mode 100644 index 00000000000..2534cba728b --- /dev/null +++ b/jans-cli-tui/docs/docs/models/oauth/edit_scope_dialog.md @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/models/oauth/oauth.md b/jans-cli-tui/docs/docs/models/oauth/oauth.md new file mode 100644 index 00000000000..da28b945bac --- /dev/null +++ b/jans-cli-tui/docs/docs/models/oauth/oauth.md @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/wui_components/jans_cli_dialog.md b/jans-cli-tui/docs/docs/wui_components/jans_cli_dialog.md new file mode 100644 index 00000000000..14a881a8fcc --- /dev/null +++ b/jans-cli-tui/docs/docs/wui_components/jans_cli_dialog.md @@ -0,0 +1 @@ +::: wui_components.jans_cli_dialog.JansGDialog \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/wui_components/jans_data_picker.md b/jans-cli-tui/docs/docs/wui_components/jans_data_picker.md new file mode 100644 index 00000000000..9f6ff4a0c95 --- /dev/null +++ b/jans-cli-tui/docs/docs/wui_components/jans_data_picker.md @@ -0,0 +1 @@ +::: wui_components.jans_data_picker.DateSelectWidget \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/wui_components/jans_dialog.md b/jans-cli-tui/docs/docs/wui_components/jans_dialog.md new file mode 100644 index 00000000000..d6c0aab6fe1 --- /dev/null +++ b/jans-cli-tui/docs/docs/wui_components/jans_dialog.md @@ -0,0 +1 @@ +::: wui_components.jans_dialog.JansDialog \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/wui_components/jans_dialog_with_nav.md b/jans-cli-tui/docs/docs/wui_components/jans_dialog_with_nav.md new file mode 100644 index 00000000000..8abd40c100b --- /dev/null +++ b/jans-cli-tui/docs/docs/wui_components/jans_dialog_with_nav.md @@ -0,0 +1 @@ +::: wui_components.jans_dialog_with_nav.JansDialogWithNav \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/wui_components/jans_drop_down.md b/jans-cli-tui/docs/docs/wui_components/jans_drop_down.md new file mode 100644 index 00000000000..cfe591f5ba3 --- /dev/null +++ b/jans-cli-tui/docs/docs/wui_components/jans_drop_down.md @@ -0,0 +1 @@ +::: wui_components.jans_drop_down.DropDownWidget \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/wui_components/jans_message_dialog.md b/jans-cli-tui/docs/docs/wui_components/jans_message_dialog.md new file mode 100644 index 00000000000..95ac255e11d --- /dev/null +++ b/jans-cli-tui/docs/docs/wui_components/jans_message_dialog.md @@ -0,0 +1 @@ +::: wui_components.jans_message_dialog.JansMessageDialog \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/wui_components/jans_nav_bar.md b/jans-cli-tui/docs/docs/wui_components/jans_nav_bar.md new file mode 100644 index 00000000000..8fc344035ca --- /dev/null +++ b/jans-cli-tui/docs/docs/wui_components/jans_nav_bar.md @@ -0,0 +1 @@ +::: wui_components.jans_nav_bar.JansNavBar \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/wui_components/jans_side_nav_bar.md b/jans-cli-tui/docs/docs/wui_components/jans_side_nav_bar.md new file mode 100644 index 00000000000..bc6002ed7e1 --- /dev/null +++ b/jans-cli-tui/docs/docs/wui_components/jans_side_nav_bar.md @@ -0,0 +1 @@ +::: wui_components.jans_side_nav_bar.JansSideNavBar \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/wui_components/jans_vetrical_nav.md b/jans-cli-tui/docs/docs/wui_components/jans_vetrical_nav.md new file mode 100644 index 00000000000..6258f944dd9 --- /dev/null +++ b/jans-cli-tui/docs/docs/wui_components/jans_vetrical_nav.md @@ -0,0 +1 @@ +::: wui_components.jans_vetrical_nav.JansVerticalNav \ No newline at end of file diff --git a/jans-cli-tui/docs/img/favicon.ico b/jans-cli-tui/docs/img/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..3879ed15cc8d08da2f38516349c596923f1d0f00 GIT binary patch literal 15406 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 literal 0 HcmV?d00001 diff --git a/jans-cli-tui/docs/img/new_tui/new_tui1.png b/jans-cli-tui/docs/img/new_tui/new_tui1.png new file mode 100644 index 0000000000000000000000000000000000000000..8c7e59a344c398115454aed02faebc29a4003f28 GIT binary patch literal 95761 zcma&NWmp_b*DjodB)Gf7;O=e#W^i}6puycCxXXlK!Ggo!1b27$;4Xs&clg-ve$ILK zv(KOJx_aj7uI{c{y=v8}weGqrLPhxlmx;q@BcK}y^C%^Q^NzkYA~?Tbv`ym`r& z28ybA7#wAM@KEcY@6#pPWJVL+BtId6V|K({URlZRMYN@}r9%Nl;<(c(=>@$P!yxM< zZ>W7*Jsnw*UkFB~WO{pldT{YbGSzC)Z*d}Ynwk2b9j-0!q0SYgny7=+&HBH`h4O7_ zxi*tWwN&pvO1pi4T6rdTp~nCB5awr8Vf)|Lx>-px#-Ts{{g&SU^H$4B-2YaB7kanv zqY(z8|3}4myA<63?EeyJ((}+w!~z@fpHGE)+S5+|d~c6k{Ct1N^51t@h@OtobV7RQ zX0wG$E|TgWB3Kw6(*N14u}mOZp&yY*(7&4AQf34X{j0D5%d=35YAnwL9Qn^G=;R}@ zrF)m2{;x(}Pg17xv;f$W|E-w(K>-l`>Pi1U)%l(KlX}8C7VJZ4{sL3dK6}dVzf_dK91;$G_`uq9 zeK!o(^C7vr9M50P^NMF`oT8%T`}5v8d-9#?Bh!oC&m-?FR#KTfC*^uE8B+iXv0%6= zjUtZG+R55_?kVl>2>*Kf-;oJ43fqPvUfXa5u~|r{!~!1Bg{`GxL)okhLBDsf*T?-W z+RhUbOj1rRSQl8=zRj0i>7;&LJgS8h)dtSRM3LO2FsTFAi;gTZpD?o+_-`sQ8Uoi? zzKZFv6Nc1WOg#*Y8BJb!WodP+kii{$jdqvkYBwZ2yjMI~0rmyR-wM}}ysKf_FkcUv zdz#-=gK8h8Eq^dpKSn^%%KzloPIqv@^HAfkgjKO7^6^7|P3baos9*SD8v(8}$=3MG zgUI8WVVuLoB|DP)RfuBtX|pCW@ZoKV!{q|}QRWP?@^0;h%D}GgW{xu9TdsE#GhNu3 z1TQvE!zK0Ao{x(6HASu);!2ewvK5S(1?2mJMtpuE4sc7<*FYbp{`Qqq<{!1_THT~E z){R}V3AsUk_s#SCt65*w*FC^1@5Rw_TC1RYL%w&%C3GukVEBATqz8!|d>1eZ+kp`5 zdyz>~yV2=+ivSW!Tg*s8a>mFoN7yR93MCF17lH}=5b7l-cEs+UWp9-Li6x<2UT*ub zG>Vz)F9XI;TX7<$E%z|)_A8>F)HTD4X#6QG^hR6g^)cN7EfljhlCzRKW#>s&?@_es z?=@R|?oz~sCBYwGUJSOT1`rc!qmKSdAN0PFQtifb{VHdDKhUF~fcq76j6nvQu>v6wLev&Q{ z9E0ga*4Oi)bF8~EvF+tiQTt*0Z2lswq0J5}M}7s(R0J)Jd95Snmn>_?+=op5a?3fd z56p(mu_aOWKO?GE(smfk^f)sncumR7neK>_tDVob&tR5?-O&j9Za$?K6YC}V?453w z9>#{d4TIe-&@)9EqZs<*HhMY}c%28|2A9Bc69VTb>Fu+MsTOH zYQ{YYQjkP9ad=NssF?qMNe97(sg->2It1Ag_T^ze6yal zs(Farv)a3|DhxJ%2k8#*w;&G}D`GNbte0;D_I2h~o~WUa;GRrN^2FEq5b6AGY@7Ey zQMvuW_G$i5IDNW4nJd}gfA!AKP!OhDED-JIm2fXT!~4bF&Eb$jhvPnZy+ux# z))n9N{F&=xk2%X=lC8Rp$5dzDb6e6L?LogwqraWxvpXFZ7%~EK_p!GO+-4~;NsFTR z(k7K8Jic`yVmc`knU#p1wG*M&wX2Rne^T4)r0(jnmiE<&B|%AJ!R#DHyg*H+chM0aO{H)1$!Won8lmHs-=;NAw z8%ngGd3io$-}PlaI%q!U#In7hl{O&N$-__>Kft8fuvV(UBo8hyAa356><;7gO}9bL zIIq}=Py=L8A>!s5-mEUPyOSEUcmzaWBF(tN#oHt?>jf4nW&*B8@LjBz&<*8N0=;ie zHm*l?Ez!ZBcJ3zHYjit@DG8?Q4EuBUSpHDbAk*9sbyR)Eb-bD~kV8HHnykabYu!l3 zXojw^<`J08lC?E?g`_9w`c|`i<&QE*6D;xEza0*xY+2Qxm}pb;KObJ8l%N!tuDrFX;?G<+;lj6^ckFkTJvJ`Um=4H!AXdbt>YnC ztB{H|4N;IFMk5!mAx2X7;uEfNL38_)y&dj0&C-AThMi0rtFXnM^1>avZwt5j^@fK1 zL^(^i=W2`L45UQ%7_v~h8z>?AI&e(6PlOsmMsoWdcX_X@PE-Zk-LYx*kVFx64QJeK zf+D7qOon&Y9G{0^1pRK`@Qe}lv?UH#{`40hsP`ACF4|rIEL3r|8Tg{`kO+1LS>uB5 zAJEhJJyG^h9wQI3uaqQZJB3*mWwOfxh0EvxL zQ8FNiNN-&W7MB{8zg?<~p)=5Lkf)%c4I-#hPPuarcewNq&V@Cr3> zI0{2^OBVpQrEk6F8H3h_eL^X+d_%j1^wKUcR%6F3}v+WrhIJ<6MRz|Yp*|hR^sP*RHlW~e5J-_rlYFnlLU86^;%j- z?PV7yuI(6$lmBox{C**NsvGCx2V5hV5;HXuq->CsvI~i1QZe|5C)_A}nK`(0#xis$ zAs&1K39w(DsbXeff#H(NX@M+D-@*ZA>F7*kc3a{xl{>2F+N5e7u6;ZXW(#Wc9In+M zSov4e?jk=8m`L>mlS9KUpWX`HcgYV+$b5d-we?`^DZ7ixe+_XMUDb}!_n)Y)cb5B~ zj*e5-Pd>7cE5+yT73{r1-Eb*h=o&8<&?s2#&VA54TS`f?G@*?`a#nW!MC?rC1Ua9o z`D!tHkN1n7(A|E=$bGEOxi=6 zL^DAwDYKY(FG@gc>!0DlrGeIr0HM!1xv5}ezAzjIW%o~&shE1bcuG~F?lBHXnSU5! z4O?O)QBO9Z*)b{8%mi0gWrM2-tJKDLY@ZQhvsz;xP_wpr6ZDpOYcXY;`g60}ko7JF z(I#D)s_jI24H3X}FOt?1h7}~6yV+S2!)0$Vf=8JeM-3Gnd2By~&Rx^u^=6yEWa*&e zaoTaAWV8l^#bs`utsD+4wBtt!NaVz3mfEkzd}xt7%zkl~jL&q$Xy(c6zlyS`YP05; znJ)C&i&C49&!|H&yuIyEoQE-<6xmeTm0xbCB-wiM*dsgVOUIGvd*dpqfVX^_ocW`ePTn( zQ&C4!2fAHI2&47wjAg7JMbefL6nT4hwLfO&NsFVJVe<=rSIqrTg$AdD+)_A2F<-;Y zI)0{5Q(XGzrOF~nj34T?K_y_3&E|@(5bN{fFoe%rFV8;VhcwBb^Xf@`qNqOUOe|Oi zc5?WMLqn!{E4SX2XYz;E3IQeu!sn)a!1Tn)ie;U*>tyc1=BN+2T$hLvO;Ext!;ieJ0;uz=w%}W&EjHo>N*JW@R@rj?0yocGIR54VMo*yneeUDCjVwcI-S>+<#@m-Myj~~#CoBHZG&lB> zkGgT5`U&XfUr=wpxK0F4@gOu~_K??{Xj6{<=3As1%a5O}svw^;XU3sN^q_*#q0d(B60}*Q zZ?90mP!@75DzOU{+Aq&~hzun~#(%~~jr^Fg%=fyLuDk*%r?w;1yfc{4n%Li;5I#=Q zt6_WB#Gqv)(PBCygv$^8Ja35%lXOQuDl(27*s7wvkzt#PumPqTD#IX8_L^h*Ufwa| z#lk`(IAzmC7iiQU@3b=Xoguc5&AL)! zwKB(}k2qDXy}_ElUMc|MUm@b-2;3e7q@ z{iFTAOBrr`yGcwlbmO#D-N+bxS=?-!1JfxkCZdQ2RhAYt;4FyUgI)DvMhNR2 zw>sop3R>1mld)SyyIAdhe^~Z@BpN?4-#YhnvO72}d<{*k*Jx*ED`MW1Tx&Vp@{@d1 z^*P+sP8UdyfgR7>G9Ghe3`Y_Bnax4Ki66BiTfbp?4`Wrb9~9MJY2U43W7znSf#yd1 zTR!OzZsMSPL>`mVRm29SI^s5R746PKbI!hshOYvzu4Que7jN}3PkM_Bsp5%&B(z?C zVkR#|msL#l{B*U2v@?b(F;tjutgCZt(wdkM$P`N9bwm1>5j9py!x@Y#8 zj!$<-2Co*9tMC!tk`-LAG{k!%Yr2j4JY2WbN2u}JSx(tt;fyYQ^=;3W)_v!^idGCX z4F0?jC+ToJC%!!1@|8nU1?+i2PO>u65dcq&Zg&!jR>+*v&V%VrD%4XZiyQAo4vU&8 z*xNb`%f?bg4o&r#5Hjb)VOb5`+Owb5xrDo!R@}T9U8?luiixr6p6u*H&b+&8`+yBQ z<9r$sA`rW=Jr|O~=|P7=Dinn297Z-N1NX;1Qtg@GGWCVERLBuu+oFDB$($T6sRyhG z%*$ctK_Lj84dZa!8p5|BSb>qA8~Za5CH#HMpcZBE>iGi=JBgHXy2|^NgI^5}`($qN z2N7DbG#)8);$hi@j^q#U&yR^Kfust@qk%QywMK3mp5~t!%kXbNv>#v9I zuwC`j{^ry2ytWtL_OEBl`BqygU4pRNvqI~&_*Nv|C}%InSsKW)3;en1{ClqJ zTrqcowH%>t=H^pvp&0MC2sM#?h|!;lGy7eZb5ER*k7w$YqoZ%#P%)`CAXDfxBU75a z@rEW!&#xJbsmEtAF7@aP;((!Z+h}YO$sFZC1a5Apu))t?5F4eqrlpW!=KEUD+`8j@ zsb{11SSTk4>Y>(?MR|Zkw1&1!cKOI}{9d`h!wcMYA5rw0=_1_t(%Sl>6Av1k+^9kqn{dYQ{ZYeVhL)oF zz7o#cZw__&S(@i;%dyHkt>>C=xm@cnw^foD-g_#ydz04<6XY4T2kA=g$S zV{Kemg{z&+22MI2k?0LJ1V&waB!}$kj=k}UlDb;#LzX9y>lwR99pG1&%9}d>bn(Z& z@u#_vjHZID<;xgJOaTlA{s+Hbo zL$r7TKVU>|&XB~Me4ksv=Ei#|Rqb?NRfrT=Fw)scLG$OK=I#E14_wlbV((p$EbG)f zYMWU6w!meQh-p!YKp zYDIy`=@A_skqT}Aze|>qD-b5DP3uvY->F)q%FKnPN5lUj-rK!V%Jo@~f9zQh$ChYi z0|Ll7I@&0)V2Db`s{4@%XUz@2q)@P70tU?T>G9XX{=!WA8DR_KLePR@G5ssP?HKm* z;%9AwO>e52Q2RmvqRe<+wYYwA-K`r_t^MMwM_K$h$#s-ZYk=D{znvWNH&S}1d8Pn{ zC`i918sCig)DQ&-yhL9>7(;Dx5_i9tQ_3I_3Sm7OTsY5=>|)A+q@jXu8mOaTDW45y5wxZ|&`FmfO$!XUVT8I2drV>T7D)82a@K zJ6LAn@z2$W4`KV4u{$e2rJg#f652~rPE>(d{r;^zmb1%i?NYn>_nosXlTOikdH0Ph z1cXmQ;34gP?2Q#}GKwbv_w~uwRNaaVjWO&ag1L`|jMCk+HKVsvE5ruh_Lc9Y0JD~j zQnf|xD&QgYHN?Rnck(cs@@IA|@a70(;v$?Iex&>Xj{}1>Pr)p_7=-``O#jApA0Wf z`2$=n^cQN1tqJJ{8|^S&6>DC7+~>G@Gd8JHSK!UK>5Iu`ij%GyFk2OP^Hxn_D_<$#W3omlZFEJ>EAPJp|zA9SYpyIvm-&FVS9oByhHuRNTbv z;g?(1Y}!Cks}|Y3oI`Wu3D* z(CG*ycXpnO8IcD!h`o@|>V8C&;7J%uyFW5>u5n=qim`>}5a@oBNL30z zT_1^^DXj7Hk3^+BmGzfz${RT>ed;W6R8Isyr!IhMt41MQKcILu4e(#m6Kw@Xy0@L8 zAVrp*VYp51+;WSs{DG2#%ZA!)?@J@4e; z>88lZP%MQlNaD>r41{c;AOI3GadVo=Km{Izo1tfx%ES1=v+pLiO2hz(kucCE)Xw{h z`aie%jQoFQyVIl$1@XYUVtLh|ukwT=i)k^iEYDpp@SS29OwV1Us7?-Fyev4mE`UOJ zodC`GH1CTL zwFmf_a{41Nim8qI`BJ60rdDv<&aKP)0xbC)D5ffwJFbG-jdc1+_R%MCR=PegO}5!# za&o$We65<-tih0MGkq7lM&bjj1F5H~Q)}>$TDV&C8bF0FH#=(~NzylIzVN1EWBD;V zJQ+wYFyjdQs=dVFaO&A9?CZ*dDOunuo) ziapNp&;x|{Fm70_zF(2F>$n@%ZfH0lyu7rIy~QI}$sY#NzjXN1s(6ys8~|Mk zA+-@*MzBGe_ zOkuvUT`SPwd!iYAb&>6NBQCQRGtl4&%Y*V1e8O!|q-)f?CW2M0_PT2DnU^~XW{1Ol zJ<8r~-yGL|)u(6rbihd+<}I3Wvu zc>KuTV>|07SDZw-wdQ+G`n(RbFgHD&_qrB8!*pSSVR2uRlVV!N)vp#oWX*VTw|D83 zDz+rwzwQ>0W-Asmo-D=|S#Kcv3wc|`IB}o=O&&h*dTp>z-Hh~ost67La~PI&lTWcX zq-{eu4ewY>RJ>-!(mAk%p=|?}8AX;qjP0uEXI2rg?cy;;M$eIbDyEy0(qXv(LZ;h| z6`Y(otIEUXgpxK)bv`m5r^kq{`h8nJFkXF=A!*YUa*^!dHi&Dvw+)XvS-9xtkXx%V zQ-U(fF!7W7s+Q^9(H5&ypRk^=KZ#`9an!9CSo1$x>?^VnmEMEIE8x4K?@e~aMbM2OL%66vjN32at=-EFaq_Q)gTr_|Nm6B!Z@ zb5xA$(S)hIzDLEi_$5nqc zfu0os_dk!nouyk;qDStH!h@!XVU4T&0*|k^K86d+kz*8KB0T=&hf4XA4!CLV`+G% zD;qW6L$^uOpEH3F`nf+P*kK20)efhFh=Pr-ACkv2yBk-2O?1y;iq49&ed)A<<|f}> zjJM~x%Kho@CX{-rbFKo?Yl<6EqAtr^wM1TmV9;M(t){cY(@&PlrzzKEskHzrWCC)* zb>i1~S?Jx-q7f9eq|$duG|x;VhwTNgUIq2@%i^q-aGuZ)&DbP{WDqa)nL~R#xa|;2!q(n z^IwxTXqIWw74!N%w$PXU`f|X9550oz6G>~vBbo_*$fR>D`Q9^Pi_bNjJNql1q=;xb_@)yo5?|ya*%cd5Oj| zPSSa5t!Z@j6D6G0-Dj(%P(_?5okS3c0aZ_IY<8H}f1g%?q&58TCUa`}HS6TK2Xs!s zXU;-@`mQDNrh;V;|8NL4IbtV|rx&_H-^V_(W3TA9STWnCs@#A*G5Ks&?6Pnu#N0spX9uRazD2&tZm`y`<5V=6AL$VKY zxIlf${qQ&C=3<;SQ_4(F4?IMK+)Zy}!C`L+PYx2zyMO#9U=Gpj2)?Vj)muEr{cs8{ zNU_%;@n%TsBq@{8IxvrBK0NTsl%f)$8S+SEYOAh@)=wNZst@2dM}iNyCw)y*sPhEt zdSS89itp!(zGk)2Mo3|!yr6TP)&8RAw*KC0Prp;Ox{4-FJr~rr5J&?hKQie1c`e5v z=ceAPG0T1{--@B(kxBuz1Pl#;AP6-2e=@S^KbHDONm1eEv^0G&Ny}tjd^Q-+pjYMG zF^iG5qhH9;!SSZ6Fl?c#=!R$4C0EIC=&YxP?%y2tV;6hX9MXdXGI+g^-g2$17gK(d zv{`BX;RwRWG-&mrI=dWHWi)KTGHAN~CROX!`{m%5CX1o8{iUoxR%k_AI+Eh?yajRN z7p-hp7xp@vWx3cXzH~uHj0?YXWW(t=G9`Gm(hal3ua48)>$i7=1HOy~V1LwkMbeHY z7B9fq4EGwo>+Ja_n{!RA)?>!qsWaBd`lqxkM;!O*OE35>W? za_>>8o~1d_`r>{^DhgujrEkuqOtP97#X!p0orDz94a9a-S`~Ob*A}!aLF|(H0>Hzy zxZq%GSi0)NpTp!72_S-9?>@P`h9$J|b*D-v_3glzTtlu+rVT$>I1-zXO- zrpm-98-+^#@6+ycW-IjvhFSt^NU_KAAOzu=hFLwcrHn*r%Q!LT<1X~Up)gQb)UM5# zZOS!l`)B?rUuTKjj1bJ_V!>Bi&-M+AND**G_q%g^J9pX|>CIEVv8_ow(XiM|q$_)y z!qZs<+D5Z5=*u)dvoxZtbNmgMs3Z1UObAIILDX8~AUV5|F!B9DU9B>2R9b(N21va8 z*kNqy41;Qjb##+#;=RF=3zjl_HVO=VUv_ujCMA~|n3y)czE}q9;q!u0t$5P=!?6`i z39#9&8`EL7Ty}V$WK+QAyZ6?~nR$IZ=WO5P6alr7cDwW-T7o>N%5ueufV8o}n+n_8 zJVpXmw_3S2wBGZx%OMZ$5=!3I?uw*`&nXW3&GfA$VE~%A#fJ}xeEq8B-I&RGTA@K@5JB_GaynvlD>Fmgd<{hpb)mt>tfQmeQ^)Uc;x}5@pAEII+g!XZ_XHsGBBp`Um~6#ftu!!6##@aIA=;51ohcUXgRFzs2J%^}kDj zp309S3y{N%I)7j`cwLa3m}hpCpGxK?sIrPtfn{2rPKE9AETs1qS9ty54%_!*Oc{D@ zyTu$(#9!D-9R3iMRpcoCVp*T~c7V683maoY!DciwzTip;Q*W6WRh8iCL70Sc5cvKF z52ziK_#bdejV@HmOj`~t<43LD*3si5C;a`Ds`tX&v$S%wp*j2Zgka7=y;syDQ-?Z6&0OHPOLTwWgt?zw?xo7eH@R9{phF0i%j9#$SX#88qFh<*@a6 zRr~=dp^&YIxQ&(tlIv}9y9}*}$+lI}qjk^FJNvHxL$XxmJT=Vo0tb&9M08K4#!3J7 zhA%1`f4z(KX_O@3ME~q*bIpNVypD?T<_keAV@AY=>*Gf5SvG2|dCCg`O3~>g@~`^z zzPrx?Yz%GaQEGpOVsgT#>i^9cO=*YuKR5pqtw|Q*0TQYHyHKuJMGg#Unu_`tz4SC_ zGeWKJUxoRSZ2vE{lH?StIPhP*Q<+qgNVR`aPw9jrX;fNw)?Ijz3IB^fdM8<9n-Z*z z+4kVQ^kmTH5%B5@+5Z;#KZ1+wPLw}cYV&9mqg-jnOHckERsM&_=>sQ>$X-Q%F85!& z*3dh&0gB>(2_{Tp@7qlJ4+1Dsw-4OEpW&ZMRkQoor<0-|$kG270n|7p4*tJ&IU>3D z>(jrOr#_~X;7C*ji@%NMKPws3J`Vi%Zu;x!wQb z!^Xv4x{3T-%0b=NXwX*RzjW@&(qO&Vp*<`1s!%wCEZ>HjZMBODlJMWbx zTD=hXo7%fdr$YDtlhxb#-yL)@Nmn4HE`9*|mt;3@|E<4&Qz)JA`BiC^rF;)l|`&5mG- z_t_`WN$>ZM&4Z~&`!OY9)%?F);g{x_n+inKt;M?^Dt|?-uOb(`vVSB0SM#rf@YOa( zXyBsM{$V4J^G2^-C@dODrIPU=Qp5XzP^KUVO50-B$+{Q6Ze(AYNw)Mo=hMl!Q*9i5 z5Xa&wMkLE&-~^8bEkOElBtI_ z=V}m_J)7AUIwn&*Gfz2M7*N<#Od3SmCe3HQ8;roD zeRyui_iXJdj`iGf*sLjN)yv9=JrCc?Mr>H zc)3*wfCJ4Vc3Xbg>*haOkUd_0Sz8_Z%fDPi{AI2_fdfEDv!&xb8-MxZ2Sdr3{sPFy zg%aCHrZMoX@uh)_@I4We!YXJ_-eS^yQf36HvTt_)0EBG0PdLkAaHq}mga#W2P2`QQVOr4^QM3C;$JQ-T?TERo8@QFdQ)FeN`1f{Z9N zpfu~|zfD_c69QoqF&rUZklM~rK;O_3Wt2`UL$mj{(4sEXEs2t3yVPUer%%ds(5-kp zKE5V!6MfnH``97a=4ZU68Q}uo6%}6Ldo-awtsV+-E_4fsWRAB~=V2Ds46dy&MU?^# z`b{zE^R3{A@nXB*1ZNAD*a2@jKjsb;P(Gc}p@W>il$r@LLufWBuRy0w_KTEr@cnYr z@AdMp4lzcXS&9{NUq&xthcYqn0{GN1V5#YN->s<%SWN0EazyvN z_is{sOj}C*Yr+<0dv(bvit!UYSd^#jk+HIEohGuXHmm8Y5}%M)z3b~6Sp zg7+%aN9hlkw$~XQ-z93R=pgY@R3MX9vBRyn$?J8lOD)HDE7u~R;a?%a6n8kSNfrhJ zfVx-_(KO8Q3204Ml91z5jbM(f9*s^G+7j%Gm#yHT~CS^XP;_QG}TlPk{zrm;rp}^;XzTnaQD*=;-gJTs?&&-6FXo@386y ze?6>E8ioKNXWMu;f5JPBXDOG}V*1BPjHU9b=@^_hymsuxRZC&PH`)zd7FocRVkBGN z+)7y{c+_EPRbNcUG8K%>39Y6=oUp8wu6UKeCPc7tr02VGb?F|@X{k#@>b-iU6|8zF zovdVhy}D9O@~z%B!?TEgX=Jr(yX$3SpIp9VHEK~>QbFH z&ctvV)UhL5c8?3w@s}8TW}>1PL>lYkhjwO7 zgVc+?>XzlmE#`{$^PFmg*UFooB@Dya>8covC?p@B>dwPYX(kp@fWBabC)Y(m1ehSK z`@nKULiCXNUIC1?-=HmqIENY=+SiQ=G23^oC4zy9If`@@CJ}1qPT1v|e*W0T7ZO>m zz0@pBY%D+l_p8W`dN<`1nL}NMUN=8kf>Q#*kc**;|C#h3`)>&WiO8?hj_Ob_HD&fr zQY#)QG`!4Ch|TH3w}zgPaR*V(9=&)>SoFcLy&vE#epI5Ryok||d#C5ZfM!b-oGUVM ze{+GyUCEIo@Y^Uk_N|*vh>_pg)xP5&e4WfE%raE{S16TD%T^y#ptiPBaRp5`<1?%C zl(LK9^|vXHST}+B4OklIqcSy=FcFrZ?j42=quwFjpJtKBgABzs;}d|Z4~uq70n*a) zRjWN{-s)UcnrjG$fni45a}Sn{r!bLf+ekHkqPjZir5p6AP(<#yk|R{M$%1(Rg<_rA zaQ0Y{b|=bO7V%$+oP4s9r8~Qa;Ca=AFodAP=`p-c&~^xR*e~^OXGMDKPMn*SQ{+Up zgAwQok~U8cKDH!r7xdB|pJFe6RkNpY)pk#2rdJ3`(s5HFVKQww$Z)}<(q%N$!!KB& zN34>w!%BP?Am@F@QPRI5W|m5OUgHB`KImQ7g@~6HiElI|;k=sh>2gk`mT?L~Yb;MtQH)3IGPjc_S$(@y~OaunBzvR)yr0DRQDH z37EcCuj`*`nL<08u?W6rsyMN_ue$Bi6jggfQxbV~+CaVh^QTXD^~fmwJm&BX26XVr zF*$6MTD$3e^Z8A>y_@f6pWey zt>~Lr3R7*8#AFq6`q>rre{QvX-!zxYCS|)!O@=|pmEZtkB8d|0o+wwq>zkMRloTEc zL60~wrAd5Z0;tpOOxL?HPJK_@yD%;GZ@WSS3D&wgp0!yN=#jQjF|T{d@$Ps6z$o*t3V)A(ZKy z5q*{GO46KYlkC%0%vm_xJH@Cqn|}~Tr^ignRRySKBRY7vHXS8?40C=g7AXgh(pMkQ z*MoPhG92Uz{6rLSbjXy>_hUw~%~&e_rnof5RIG@0ob8Hox$u1Xdoqp4RJ0{J?cww< zwR%a;J`z>!G3BhWpVzYY@xC%<{;&JD?DtH6xKA^gTASN>61>dd4qHq9j1n`1OS4L< zZrW4H5&|?c68|Px2{;OtmqF)d7$|Ul-8i5mP)J{M!F8-P^0O*)ZS1mQU@z@Q)fB(G zApxODMqEEZGn`Ap_;i;ieDhC*dO zp`{(`cWG>On(T9PE?t}CVUuV3&NVCEc&g$yRkMWx)IPN+-GwjVr_g2-?@{xVI$lEC zUOsILlHBfsEj-==%(*5T?R1rg$7WPb7YgFS__x}r{fxP%yOdF z5AG`G1M{Hqze-cTMs26~p5jLGI(_{(JfPq*j*D?HFL^sB+NZ8m? zHw>y^hO@aVgiZzeEC~^aiTSK{bK=_Lo>RFafz{D?BNjL*>3vQ^V4x{MEX8AC(7C@#utn^E>+4IAx=NL#kK>M^(5l> zd5+g_tY1cpl+RvYL>PNu9--12Y~-yVKvK+EFb?h83{(7+Cxi7l;ogGKHwpCgtAhf` z78+{Y;7T-A{FKiWAQqf`AK~fJ#~Im&H_O0F^b10@-jrg z)|k-$3#tC(Gr)xPZSsuS#Toh4sa-YtW;j|=TfSer*u@RAuV28BW5{mk0)|(S2BlYo zaDJm;;E=Plw5LKU$QbQPR+E5`VcVCy$DWNlHmXY0_pAhj75WgC&GVa)BdQzyhe*9; zS%Z#NUXDM=(>$erdLj2-$#V)JQPPGSi`y`5c=8&NCdXNXUAGp52_F>!e zU8^?atLDlBTr}#dUW^M~qQrXVBn&Vl!;n&ApJtQ#YSvapav8D5cY}L$5L;oxQu5Hp z&$B24`=UwFlemlyvjTA@;jbX@S^O_>C5tS(3f=NWo~N|*ATp~!U$hw$1XPA!rB=bN z!{W&h|;GFIT;&Ume7gH-8_c$LAnL_h@a=`MCB^!!s>R zL8Q;?F65uCN|=C2rh*I!fi&0&4lO!J1)d38lPNX>2T};s{f3X#2BJ8`Ghm|pcS~#o z_;#l_Q5V~PR+OW<<;asbvEIotGEZC74;VkP^J0~417_u&GBp~)0Qm;A9~b+;;!;^w zz3WjOtS)EaozhO#b`b-ps7{qV6*mfOy!zTeyk6r#sX{wm9B2Zv@9Edt5i6WY(oG{X z^x9mlb`=`MJV<(UGN37_LEP*`m<1wTlTexMMNC6;^l^5B=EmXjrx$XrS4Pfwx$=V? z5(3Dnl~s-!mM<;64)b;3C)l)=l4EN5(OCQkdy$^SbWo>4 zFC0+D?$QrC=Y*l@0}bI2^3|cV;cD8@vy?;h(DHzd6dhSOl^GG8s@os;lF@ABD*8G6 z`lSRR#l}$$vMoiAS;ObT)u738D_U}Ag*7_>^^ZGO zQL=daN&<0}5#j{3pdbv02Z>}E5UGM#eyqRNyGT$2G>8JI*tM%m1=SZBA&N+tz zLjgO<8cYP@RwUu);oMj|U;s_A-)7|r3*#0QTWJwTF)+*RD!R|$OOVUZudh|p4&uS@Fc zhcs8tKi+0sN1C%_{amQNHqNAx3ijjRjED_|WbXo9Df#%iR;aPJ!P#NAWqCBx^Y{-$fnU0s|le$SK|nv~h)# zjQY5J>)p4mwnMqBLLteVLewWV_5;fbgaTREOtdAUf`2R4^93C(Vujv>!B}wjE`AzQ zO({=?lBOo#5DO)Klqu0;gxumwif#I(b^F5D1w;CIAzXd93BZIY-|sVO@}^7z|4OEUXRaPdm#W66?)f@2IOR5&hTuAm+xtE77w69= zI_%;{imH%wd2$sd=g5v^4ab4+^ZLMH+Btojj5P8%zrW~PR~YYx-W1{;&Bxo8c}rVG zTs|{q1z(th_m%78rFPE7L6Calbdukns7KOq-?3cvB9l{U0f+9znE%i(CetGTW--55 zOwLs^InsFmFKm<1oDWq~ixs}TR*dtx#*p|&3Bmy|e1ICF+C)hJGA0|nVFlMIV%1p< z<<#JK1n0)ti-IB8qk{?t$_w(o2_Wu^3<_51a79jq5C`3cwF;|VlLAFro!tmEwM=I< z5_F~_txUml5Ph|Gn$&DQ9GE^G%JqE?iOv3dqGY&7j$oO%seL)7?qZMI)Zt8_uBBg0 z&Rr_aVmsL0O4PGeHZL;I4PXA)J)k@*|C$g3i~dT}f&fvs7Q<1S10{+Wz&6W|V_~I` zSwRp34yZ|*VWRzXb(3E^e^NV2TP;m*df^U&FpC=BPKjysVa{6VGt&QU%6JkvJ%B`! zzw$h2a9{t=m2VMRmR3PKLO0o4=#IOp=#y%Z0QGIXR4G@5h|%YS6vhZOF;kUFbinMq zP-S@m>F&1e_>za{7|3PC5z%(6R$HMyFw0<>&; zU#Y*4suY|By+|+c(T>$C0Z)Xlj?W1U2}3I$D9HDxypAlQP2?T9LS|CQTa@JH88nZz zXxJu;YB`P16_{tk$Q1&xPO0`OWwF*2oQ7Xi0JoKBPNf)GlkL$dBrgKcK+ClH8*=n> zRh)yE#YITJe^lKLl82^mQ!ZD;2Y2IC+bZVBKSYJIed%7><{YCbbc_~j4W*r7ph_VU zJO-Mo5+0Y4Phl(5Y!7@$5dSeRsn$N0b7EfY>`bD%;;deLQvMnx#Jd)rxYwY6H^utaF5x&CrEG5o*LOIaMr*c^H7~7T=4$x*Vy*FA7^a;cAiG zz~G_^P0HQ0;rfp%hRdo6S0GvF^y7IlHv&MtoR?Ha+(t3FyYI(j$`N)bmgoP+)ms3? z)jjLpZy>k?hoC`&yIXJuw?TqSAh=8L;0_r)5NxnvaF^f&f&_PWcMtyU_ndqF_k6dC zqL`v!X3t)0P50AJ|N3Jn>NPvWR`5EliCqRR>G26F|GSlujatGxG|vmT-xZ>eL3)HF zrW{_<9H9jlJ$tIC>I-pV%o~i9d!s9F7a8Vr&kmQ3g{9~5D+xWj#=@*Y@ zKy=soFVTJH2`3{xRL8*6Ow$6z-eW&p+dE8Wy$d&jp+$Y{YvpXb+cn^JU{zR3mD8^6txfeRPO@*B91m zkM2bAhw%Wr8x*l5MSBYym3S3yA?pn+B@Ha;@g4=DLid;-w;X%=hq<7#w1$U)P-0w- z#9IaiNxe<=I=2K#Tg!RR4=&h{$ZsUdBNcOlUeYYc76bFct0%fbl%)ev#JihUjS13L z^fkB!2#5nh?nw^EL*)bsd|L$3Zs9V`=6fa@atgUnQG+NR@E(r_whPGxuZt>vq1(8q zrf{R8jlUA2EF}6ss4M4Si?y_8nfY8#@|s}Wnxe$moNTjg&_!>PfWcvTs8knkn#IW= zi6F~@kmEx{3`{~bv+(hqnRj4)K}&J}VKxElh^%7g_25>4=JM(sjcx{GG1=a5gGfW4}x&m9RrF898*f}T`|9X#tmqWknd-&&JvB{|^!I8d_y z#29Jo*eFW8X|NFS5{8J}FM{%D`U4UeY_-g|VTC9%TSKu7lwMuDZ=c;H?b@ z!mhi|ngfg45U(gOH0$TjzO~3bEtLZ;+q4@G8FvkGcJ(&yD|1e+ulF=gKdI`MnTws> zv5VY~f30sT)UXAutds1~D9N{OyVgq!3XhYF9Nx-!cPKd+0%l8@uV`F5P+PnS7>5k+ zw@96#Lkyb-t|yjXb>$b4mA#HNY&A4$He~sNGRDkm6gW)w7%+5s?IbK4LL!&FpjK$dB3_0i|4!^6pi?R>x zG>`zSGpST1^b7OaHyaowStW+2cqR9fq)gaX#M{k9>e)gUt^Zv^d_H^}J`VVA86sm2 zVc?-Mcd|Q5&( z?KI*cJ)91EhOv)kBFHawq??DYXc`jB4o`p;{Cgle-(go=lN&--7HLahMvu@cV-^MB(2wM;frcEE#WE{Xr=N!{7tdu# zH|=_rP)t6su)NkF9VN7G9sf&YgASAt)anWawFcio`pWD@qa({|k+~)^U-zcYsFU7` z{jhMB($;xjvIK6pY^hG)d5^!9n#9BI;0EO38tkbNsgU+sq+ScV-Yg!+5Vk4{R(Fww??}bE#HWt%`@+YFOMJM}RsA9F4iqib&=R8x z%*DR24$}7}YG5+lN93_x-(r$Re1wXEsUYEHqUUpp+qj<>>u(Y|xk>S7w}1`W*&=y! zYx6YkKtbKsd=^)eLIf2Dg3|3g?j`8Whr-OSJ@=uKVmE^~@wM6TOaZPOSTi-V}bp zGZB+qWr4n|jg?_W8Txa@Hn9#jaa@g~m3wy&T6BSHjSsyuSic+VH=LzVwY#fnYX+L< z%-=>CZ`Ey65RuO`2?_hkm=(H+P7F{JR%Q{a?lNtXC=}q0V~$g=r+>nYNU|MB+)F8> zh_D0{nQ2g}ZZ;^zfFDUf8^7>4h-T27Sg77S#&jj~3`!bmo@|79V>?9t3uWtdly1_? z2CH%DDmmA657!Da5eiTOQVK9?r3q|{JT%Cw@* zRR1+niZrb}Y>z)!*_58my0E3DNyEgWdw-wjtTV8_YD9~kA2voC%GaC)o3z&OqGR|EEkb#CL_4Mv zX_Tys5La7i_lsIvR^|m#Yb=CIjA>q-Om2nCmXc-+!y#$Quj+e;JD)tVJh=6kDQ89} z^sE7;h|*(D64W|u(#JbVBj%5qh(YQ@>Bm0HF{KDWoFayPkj?s4`Z7B&1AU)jWKW^R z8dxHAS=pvW$8Yz34PjWM8c7My5q%y_1N37!8UFpN){cher!g`pPKrqO0e>$y_HS#< z)gqrlEfY4E=x?uOaqH>uOF}!-VhyxmY9&UYbG30hH8>;%Eklb=vHqBzF#(l~cKMi! zc1-0MG>V$Z@;{{C54ugTqWj0-l`xh!;gC{I5z}OxWVdp}N4Mh`@NHYLCC`*#uR#8W z%`jEB>7a@{z~vPdQ}av2WJtq?+&=2^^Z)v*@a>+L=s_lDu=2A;PDSAzJ+{V6uD_yp zl)&v1BTX!G(c0Vc%)4ZzL})?L;OZXJilSiH%&&Y>@MnG9$?H!~^le*G**fIcz3RJ+ ztQEK+*F_h?@G-;EJD-P7*~3s#vT7Zsh5jLim9O&yN;4xH4p9OnS+_Hhzff_p4vC`* z!_fq38?+}DW|{On9cP%>GEAh7pyDH=Lt%qjNf+Evy#Wy7Wnvj)Dqi!r|6NS>gDB$p zKO6WZhfIEWy_F2`1AyoTB3&|d(}7x_a5U_ZXH7v81qAf{3=s|?lPKbgk!CAManqs@ z<`-pV%KRMmDHHQdXTOl0$ie`;#fybISyKpPuHt(n1%C}HS0R5NI)9kVW=CAj#oxg} zw~u+EKuP~!W68^_FM#F9FBp(BX{H0Q&XGGQUF5e`lABA7X(->8{`vNePPs(-$#LjOcuuh)th2oItmJHPiWj(C1cR_$84T zYe6GO)A(cZtcAC?Rr_e`zdo29lm696pk%B;r=29HGFIoJ6>g>|aXCjvchnwUV4?Qx zMt9UNbs3tfxVXKl>Ss!lYTHL!8$E|Sfq9OVy!b?n_QochI+aV1HY<-Hp+}pRv%7b>ST6`4{&+KIsJ$0nNi$5}(e^H?m;97|0Mi^;qFBPE7v#;Dk zDbKMb<$EdCvBJ$~jtbL1G>MtpF`d5-@I`y^0rwo9g`Lk<#XN z)WY-I@hY(`*a2s4<}DA=vAq5ag=kYsNUXF$HB9R@!Zhnz!A>wTHWtqQIKqb`|E~{9 zzKp~rQ}$!I!@{3dn?qu$7T!rnM-4v&4<6xPP>dLgMCC~RSbJ887NqaRqbwTcblyNF z7s&~E0B~QPect4TH*7xB62tZK~3RD>O&6l&BS?s1p!cH(bD0HXl;R&f+(p;Z!NXxQ# zl}kX|Rtx_e`~LZTQvS2rLGeRf*|DaNB-_2@zfR=d+THccWK`Z6U2`o2@mNryIhrn( zK^P+w#+R13&g`T2O&+FP48MA5sTpy_gag8;7WjipySM zX|9u1xz!?%m|KhIb&5Ee#pXy(x9fA?@BR_MX8o;OuAgtT=}9toXux{(7RxE@Oo+Jr zonQ5x_z4q!Nda3>{2AYsq>S@xsZv71WtkAQDW%YTrm4gFa@It~%9T3}BPrIQ#=Mrp zpEL#YtUrc@LhgqBaI9fvC#l?oIt8=|iWd_av5I)!^W7>&TrUX`7%DA5`+VSh5DN@vz3+e(K+^XZ@3lRr7X{rHRI(d=eOl6t| zl-I@Ia-bj&@9}q`GtNmVyK&Eh(shkut$PnAgEFfBb99jGw9z)?H^U8v{8mK+^%mc_ z;D($p9}(&`x?}06kw>jOzdF--tk~iid~$pG=a&FgShHK2Sr8PY-Omtjzk`~I7YQB_ z7LSY~jVSD(>|BrQSo_`XKlZ*IMb4c(7We!dwRD^D*2@8BV;niDLqD1jA3X-_ZGe!4 zqbK%{3dJE5(##{8a(tHPPD<81s*Fmd!MZQ4#_yj(8;*U_x$h892K5!&@xEY-^L*shFV*!MKJ_8a270D$2w&NGb(*w+jJldS`jNU zo2la9Y9E#PlP-*ylt?@NOXAs|4<)pz@z|Id%Pjrm7H41ciCu~_eZpmKIBUd~9~G~ya?xe3;jVo5)M@o7TE&A`ZnRcm4$!Kcuc&T# zJSmmsRYxe6+5(Vv8=iu{JK&EkO<|8Pt6JlY;eV(Qj< zb40=ao8cnL^^c9Qj^Ea?Hj~&bcK0cSsD;kH{>X@KqRTZzN{N@N^mOz+{y z=1HyYm%GQeloT(>Xr#O#|#O*B|o4H3}Yh0>G+uv{r9$XHODm&|qk_C>(n5$&uc z<~Td*`*?JJf^l{J=4(fVGPO4ALqwzN1K3!2X;T3bUTNor1p?0BC*e&A8IOobpF^eJ|lYg zG<}4-_5t^Sr`Fkd2Ne@lO=d_KXk>vAY93Ln$ys#C4iRb`>ux+wmx7Fh@J$-*6XN3% z$AyYSTJD;9LKZ-HB{P;ZzNa*?Dz-84kS9)yI~)D<26Jd-X0VOw^hLICs8$WVs#_#ClB|sN5AFeoa1n7i_mDy}v{1mhXKPhuE?< zRLyg){WH~iv5HSF?nhV-@|8q%7XIDT463VYU%Jg_18%+&^aW>YuQ&)F(mGRz)oGW-;AcbB@R17oUAtFC`%K+NkXBD769Ms)JneMcWl z0WSxVEVI(z1sRJqnB`o96&4ZpNkTU6GN%>m(@P7M-b5uE)J`z3{j-5`Rx_nYDto7H z*3W$1#p?9hPsBIn2H};^%=~uj`s!5LU|aco-Hv&^Msq%*o6~4dV`^)w!vr6O_%q%s zdHXB6vPG}Aw>hG}vSQ?`9s0gt!d6kQOJ`DLEeWG5d|qf$%(O5`Y7ruMC^r+*IL>~c zyB=PtvQn*Jl-rZT%vjeBp7_{Bo8z?2kVJ-LCn;Bi`87iml*%RN{7TtHtxxevB11b$= zYi2woA~ls}O1nO&yad;-p@H?nB<-SPjg9eguz0>DcWF+uA&RBv=JyEnTmz)WH$Bx4 z_iRT8lvl#wQ;N@;&9=>XyJbpEE?>lbM7%rYA5^-2e_Vd6a(LZ+Mb)^uRR8eh;s^=o z`k?_w9T>H_^av*%wdrvw~cjQXo*_H{LfWRQj-H6^U1pG?ee};|H4dC81+DYTR{Foy7UWW;%+&+C4m2rvnNa}u1&c!_>NwIZ z)!gyr22|xiowM*Z&VSLodum%x>O^nS^SULFs_~6~2$2uJGcg1x+or(sHAGl{x?{+P^Cii?8koeVncjzDtLjzNg1}^lBiLMb|Uujw|b!nb@+$yGu${AHk`ZJhm_QM zu6?p)aIG+wotal&YH`Deke8!Tk65@iqDf&ky1m2motUUDu|sA16jq_cC?c43-)u$v z=yyePgB>UF?IeQe{y(|ub3Vyy+LCQd`wtm$5w~v6j!%Zlim@_UNx_l`EBv^70;UW^ z{z9Z}!3Q*Bb>jMk%AKvJf!%ckHI0~(LpQ7Dy{%z?E5iIROsOHvBjsHB^*NIKF`Qa+ zs6jiYk>HkBpvauqUbnQh->OmtniYKge?J2qG#WW@b%AJSFEtA!z(0rPS zKOK}(7%^aGc5CCt5A8Yi1Q-#K)*Q{*T7N<&Wjp0^RRn!*p3xSuO{7oqGeL{OWV8q0 zD$7lESs`$`&zeFa*u6JO8$V#XU+yl@Dqh+t-F!86r`?0o8jjw%?Ao?fU$1EWARXVT zuY#J#6+{uA(>d_#qe0;NQd^<}orB2?sO4-8)!Q1x!)43skep(ilJ=gk2Vzcw8#rxk%vCX*F}to7V-gR)i%p6FKg&jf*)xJje(tlM5Urd5KCc~&Z{E8II(7E( z&Ng;XskXAEbtW0nLKm{;3wa4lR4n(V z01;+sN}DnaZzH)PsczJBi@0NL?e6E7nobx*4=`+MQ#+c&!hmlLY0lFLYs3v zLC20|3<7Nn5s9!DSZFo+psM=>fHi2e=bYlle8hr)Yt82wf$;oatrv}h{_zAfdJ zc=z(9e6@?aE}b{O^pNN3TAFvvng5|RFCSfCRTXG^0c6F~fttAy4L+cZwC6^++yN-e z>sb)fyN1n}G7NNeMOkLENz$Yu=BNU0cnFb0M=zY7rCQH^^it=nN>&-m>%A|#!=83a-M+mRh^Y)v$Vo!_Asz8Y`TB{t5{$i6Yw z9ksW;t-F?cf6K0^VC_v!eXhj1?DwjX>2i0}1PIH?f21!G4~L-^?1Y%vId1OX$?j?P zP&yvy{M)uU_^BCGXo)sU`c(C_V_}${@%5bpSrh!@xCD)e>j{S3tB;+-7U-(G#y!!D z#7!}Ep{VM?m&t~_!JMwgSfU`K$dlCgGaPS__x#u@(kk8_bUg&u?sVh1Z`~Ryzm4aF z!dMqJB0Gs8tpkMfZ235FCze{spaqgw>8m-{ZR`?>b)*{6Twu}d{yy-g6)bzJZ4l;b@sTi>3ez-Tc3Ux&IlE@>ETg%^1Im3LS@?*c@910dD`T8Lz4%vuN*Z4sB*xZ~d ze*k(NwSTeCXVwo{QHU0s>hd{#Rd6;xyN2IT+Wk3!)^r-e!cB)2gxXt68i#F0x*7Ly zd1K?N%Cc1@22CM5Vd%%nu5^BPRN)!V;0Jk;g6#i%wgnQbv*=bP%&@ip+L)Gi77`&= z%Mq7zoN=*#vSk$h5|L+lMDpj(1>4H=G*ah;8^X|_p{x9(6_usq9-%F`8!~23N5rZZ zX3}>fHMGFKRqR6zKX?9##Z zBHvuy9!jk35~_!Le+QvH!d-LsuD`!$65&Bpedphjp(@)2;=O}o6tw|0|EC!-GyJ2B zV2+}1!&e;R%fG3Nz4+>`JjHYbTP}n+#_yDvy_a|H`0o<6~ zr&uZx`gcn*$p+bJsUjIOfMa0@dvh>L{>#l3^@*W)m{@H`_nDQ7hKYi@@;*=y*XthD z_z~KH{hsG!b0>NQM(Z^wCi;85vp%Bf?m<)8ZjmxeO2llTa{i0ovwR)SA{qC4o$Sb}eGXe_W)62iYqp`VLVG2lh@X3a6e3U(@BK&xQ4H9MNCv-a=WK zcHRJk&>l~Ys&|>JxC(9pAO+rjb*YY9m&`gYI|)v;2>5+%pzCZs^C?#OE#)BVW8HbHQXye9?IS~fpHZEAB&_U7 zS~@P)@yNw3M%KPftIAj7`HS{SYZ1di?o@VW+MWE-2EDH?*XQ?j=a^&2*uV;qpl6@Q#MxN;KeOAkOc` z%3dghma|U#L+lARNq%Ayj?I2zpl4FTm&=uOlPZf%oig$lpt!2GR33moCE!gFwCz9` zEl!(iE;3*Co%fYV-AOPdAh!oa(fXCsj5$rAA1%9~`tA(>N{$yB{}2YUPndBGKpm@L zYX=n`MDKW@n2t;w&;9hlwYoY;kVK;xH`=m1Nf`SUl>YNVfPB8~6$XmCw37TAB$Z7S zpn+A0tj$~U%n_pa%E5`Z-kI_2dyYKEpZjgjiOOZ!ljX&JRk2_S6A#JF$76d((9sos z14UCPS)(h&_mY(T#3*e1B#hjb(2h4gBj{MZ#`$5ye`#1Wq|HJT=PbV57fHQ3G{@KL z&m+LGsd1#e9dR%esefY^-XJ55)eB<6XqvW-I@JMi5Qn$U?|BG)`x<1_smXL}1^w!; zNGc3FVnp7S%st3rydHA+F)ap}av^Fs`5F4d;1<{k+JETF49*Pe4^58_SU(M) zWV{p!d-bHI2eKmiqVR{MNks^k&ga=&3C#I$A~rg%_8ebn%?TCJ2iKRaLHFYt5L4G1 z^S?KFKZsB7BiF>A^ueji$Mqt4Hy+?Ou+N8YauVybOQ>bwEqna#xDBTrUrU9&TwZoO zK9Mg)=lH(QhnyV3_;uNw3f%8sW!@H1@Aio;bp#-NZsh18`S89erUj`&ZS@rlM^Rp*Zqc2MOW2K;BosGhx;C&?v^Xv`1B5mHs_6+Z%^a`E6Rmd1i= zNRkg~rhKEMw3x`3w*q}lIcghMw_@s}o#E{kh&iMVT)wkt_7o)S_`eG3Sl243VzZMx z*Z)9_((w&Y+B2dz|09EjGZ6w5k&@k52@7K0E5$Atms^$fl6sxjwV%IpW9l1dkG#dr z?d7@jEL~{ke9tIUsO9z^m&`NgcfDPsGs_{KYT3ug%8j0}e$Zdo%pE^jm=r$PH7 zbh6FhpS3IDG4Uc9F~pfqRfO+Hje3WVN}KAO5P_^WRECAPX?NbXDOW>6Ksl#A>2bNV z$<>}%2m3VqwcYOQXT?$>Xpk3*a&|LeF?+wgPF#O&b+Fg9+}_x}UD<*k`gdK&H7gxb zY>u3;>S5A%GPB6kj%-{3-3X7OR`2wMTDqE}@iShl&lipUK6*aJ)jF(yw`0tQ%EZQC z(OWdx-;xnh`rFgIx2htt(KRfsffhk|X}#3KpzN|@l}YoijpP5RKt<;Q*aWu!ceIuw z#9UVM>51om&x8WXZp#Ep1+G^A|Np=w8%?b_fARhQpc?+GUcd=}7g!kj(%tYiDTRSjslziYn*F@M~hnJJ53(083m@oKvIu837bKb5@L`eAGTat$bp z-BE?ixD)y`jph1AHSI?F4lbUE!1?G5BHnnc=GC10N0^Cgf5`?|0*_uLuS7QCoR^b9 zkepYnv89*(Yl!O1G$}_6s@Op-hNgItD9@s=K)O^1(&F;pAvv3u)j8{zpAe^XTsMgu z>K*vhZ9pHvCA!)rKo2`}S}K#RFj-!zBi8;)%6uCZ6Bb`HegW$AD)q~{+zBHZM0G=2 zPzt!*mqm~r3;*3EDJo*GYe@#CH>|$Z!A^l*o7$Z1goNjZ3zZr2=B+f~@k+&- z(l5V3UufkZ-|s4J$VOu=gLij-J3#>lSbECRR@OEZZ^bye?ip{r6Hhu38*Crq%wdon z*-p4BqYGfIpA`s`9k#9F<>fdRO&Kg>u9RQjm1gr^{`Vcwf-{ztq92yHF1}!`tS%TJ zHtfi8+7@9SGqk|WY)1~SQU1Nxf`U)O7zmcY+p-?)xK|X;8Ce+`jW`HBTC0JL6{5Sh zm3C(fFXSnD%<1rj)t7K#3J1n>16`x`Vqgu&xm`vo8@&S>Z3iV4FsTluPii25Uh*+O z0?W;>gNwOx)@wP$dv^21<>w_+%s$a5O-j!G(~>om_n6DgC{l+xcnd>~EAoMrm}D&! zUL62q%q}~k-B&d#vB@wHNo2Gox5X_r93_Onw#DdTR2F)6ll~DEi=>&I-8xi0Q7O5k z#-TtgjEq6W2i`Ztg%4kwDcHG{EPG82n{5{$4W}}5I>V6-uJKm9z%p4LdDB6Iq@^Jl z(|-ydKn{rb>3rZYIT*XG_O{7mdf=N0!x_$q`g%&teCB*br;MSHL8v(j;ocotmty6W z%qG-%s(`Ocn@qU+fYZ4goh%M78oW>h+b(}q@3{Tx!$|b5^f}_OwT}+D26l>pU2aAg zlNc4ti#Sm3{UFa!M>5$0pQz@pJ0R_zIP0k_roxk_z_&TpKGpoSuoyjK%%U~Nbmu6k zS8z|#x(i~at{FqD`OZFt>>uxHG-bNw7)jb7$9X*)toqfQm*An|+v*Cm$XBxyko3vf7ZDv02_2iMS2U z8d@3rO2x4R?W=p2tEi~g8e1m6)p?6N!%zj6!7JfWC5p^ zz&gxP&aRXalSVu5doDtGpD<^sCG*`XH`(32gxvtyq1F0s=EmIP=5?_k|EZMUD0zdN z`7xhNec!jxyuLV1X^0sKp)bjG0K$FT+fQp~k!5$xuv(4nWjA5c{-GN{y zNc*K2q+J7yCG6viP>A9F+nPoSx9cdc-d_NyyRB?Nv|9!UNy!)=%_pW&bwc#hTp0xl zFbA*UsKYq4b6Z%#gl=FbLnLjV329Ly4xTd1L9bE-F#-HJr4YjcGt4&1Y8A*mR5W-b@b#-nVYYS%KU?p|YvJ{A z>{lDwdDCP65XsRPF$~B&BRJ{_1wICIrXlGu#NrN4V5K^+I0ygfN1Ce08);)E|LXKgB!+@9N ztq(gF9NjIDZ7*W4QxHUkJj`o$BsWTz!QG+d(=g5_$)-!O$Dl1$7*OL;8>}aSV7<(X z^K73T$M0_A;NK&6;ceF$f5Dx9EuWOl#C+x3_B_R%_|WcssHUYK=#!jscMANdQhn0V z%OE@|_fagSPjd7%9|B(0IbZqvtJf`5V;LVmK7uwJSwCE?t3col z*yY>V`eO>}VUDQf;wF z#uX&KrlRnNCHy7VKFH{IN=53pB<1=C01!t-;8ktNSDqkB> z(N4iN2&WOJ)ot`X!tTB+hIDC6WA-Kg-lgC(wQ`Cjy+P(wW=`PlVeaZz#zclNV3Atj z>ou3=ZJb?zDlKcW8SwT1* z;M|4q3M7r?<1G&-j$g07Ta4av?3~gBfC;4H*u}2wBDRD?6^~?OzUK4S>Fb2cg%ZJ= z$Cj%3%@g5p{*WQW+$eRwpi9#HVTKBDXW?Z3w`PK)^RxeKCh5rk`Ca>@N9?!yh~9{ zPz!13->pO}Z^OnVF^#Oxc>%qi=!-mACLMcI7FhDrhS%D$3R8b#TAr)D6p)b@?O+%N3m+C(2n-s4ir$A zYbew`zdqUdN3|-GPWpguQI5k~IM1P< za~N9$#&{s@je^`#s8&)1(E6q*c6f~Imq{MoRy_+#gcNJ(l9Rd{MUS)@V+}9cN}`&U z7)HazgXH)tY0PCWKO3xDf?J9ffqL~2$mOcw&3a%upnM8}XQc|qE%USuL=9WZ^TPVU z$)LhtMF7)g!jpF_WMQmE{gNxJt-;N7D`JMM)>`w_DiBjaa^(mp{z4(&m|) zKumx!$lf{_$6c`>MCvXU+ZbknHx#ysM!?oiYZvP)wC4{o>QqKDAb8 zT8zWh46tb>uVEIi58(_|S5u)@PzIrqt)t0R{i}cURn|}88_r&K6nh2Wpm?0_SQ+6O zlt%d0W2yXpZn!%`YN9K*GMDodXGmc!e;xi*?ykzEjM8%~9nK=HOla$uc%UR8>|*|4 zlx>t(haT%4+}B#z#_m2)(jViK{5pqI+nR4WWyH-5TzF@#W??w@x-Gz_`vcH}1cAyj zyF%RuqG;Z0u*Cl$me+q1T6I)_wX9dor|#L14c<}bW3HvN&4_f<*sDE2=8V-0vl1Nc zBAqs+K2TU4$KnqStJcf^W2QBU(97DT2XHMO-^Wmuyme=pAPiDWy>FM_DKmTjxZy?&xbmOxIB2eA3#whoki{j7JcS2D# z6um75{fCa_>bD2eD%<-o>7UivNA@f};VBmhd8bhis<^}rD z7{^%hLQa$3Cenu)4`cF&0bnvv#=bO1A$J|Y3dvt_;IXK|H(FbQ67*v$Va`Y}BnIqW zsX-gjiY&$$Rw7a8E5!7=P0GY2URxG6n|}dj0igf+9b&-TWQX%+VXzNNrEdxW{AWqz zv%02gI1_T>->rgonhNrAW3}HU`O;_eBJMdI(xXuGfmvo(Vx*Td^_ZpvaD-{c8VBNS zBrz7LT1p`r-rH56krS=9q7dcDTKjt~O<=%-I*s;oYKI==gOJd~$v;@u@^;XSIHq}K zpg+@JW(zFsVcl!pRnUM7kC9&Znda}7jm`WfCwlRIrQ6Mzd`J)`}EjxcN{{g&|HtUcK%DcloEd zq{>mJ+5umMP@hJcnH)j=K-*}y+)sxIU!qjDPYClE%0Kj7Dc|@!L9&m3k*0jczb#T( ze_WM=(4;Wd68o!ObD!a8G(e4O_<0;84s8j_OLeYd!ON)O;~V{PefUinR2@@bnZL_z9f z@^LPflFwhJmSG@>PDhA54&bv~eEQ`bI^(oF$w6EK1(cU4b*FP=iJn=r?L^Qv9If6{ zyWrTHrq$@Y-9j2WyKS!TeZvFbXiNlxnWrpHCt1i~A*ns)FX==E-UXOMejzL$r{s^R z?l5YfONx3S+k6zFv|ZxmVFgn8OiCfYuXKUyWH7c+AA>oDHb6J#creK4n-=R-Z{RzR zYq@-ixkEvP^y%B*Y6W?UN$Y_==K_mJPz2si%9~$rs$VKdlalTfK0yw4khNHu{o2>c z^{1^aH>*GjP~#wjMX8jSp&1y~)%&^o~n0U;2-KO*Ne-fQ(o{x>tNUv@D2}b{^D>~@wZ{THFwk>Bc z@LILngwYfGZS*tD{liBvOLhK3;xJ(@p6$UH%jT(M8)R3Z7ucfn)%FKYs50RU)%Q5P zd^)mfIW#idC+%)NI8Xvi{+=BOT+_%Z!EI8j$Fxl33#3w(IiHgd7F`~n#Vi$e!nPqx zT4RB|Cjxw024mqy8GKrn4NY~0489ANg?G0GLg3Lt@Os9WQQy-1mH>~Iw-cW?y0jO3 z-XH#L&CGZ2_Zb>J_ck!v&vnD?(-mA&`ypVSL1XW3YBVQjLPZ)PF28NnH&MA;U+A~z z6Cl3vqV!HvdsZqXZ##EeM!Ya2X80Z8h zHNMKFuSXN&<*Uo%&t(N4>TH@+0qalkwUFmU$Yg2V)K>*IpD(zd2l{7EgYBAtV}QSg z8{9i1>Z;u9zp0XG3nP-`=URYisFY2+lI?R^8ST{-q5SOIFbh6$nb^HJyr?&hGz1v- z1&o{y*7ce}*^lbic*|bEJgf%NX*2neX33an*j%G1^KW!WG-bT4`U)FdqV$!RUbJr_ zEJ6?@1`%hW)6~X4ey?UC^G&Y>n1*6IWBxN|jz;foh6?PYkA~voGX39`-baf)Z^qNL ztJly~3omT-3A$ z7dP9pH1Gkh6%z1Fs_UFRnD;W8*@G<=iSMDD+oH5cy-EZb1fo8q>VS`elWtO>$X8F8 z7lEcVbzEOv@cH(5@jtJPw@4o5>pk$&))h8BE0`y4v`}XY{CewcUTUDhZ$4BVIH%X zb;rug``YR<6I{!1ov%9I+W+)@oEwuyP)XOnX31L`b5)O;lH=1wOcx^SqyrJ}$ay2S zexXT8PD`b(d+>|DINl3dn*N5IM;VKeyn7s3*GpGb1&cn2W6Aq-HaO;07Oh_o?~=RU zj;@FS!MiMT72n)#jD6$@RDs_|jiNXB zCPw?;)XxzxKk^ckcWu#S2$UhDsHW@($q-MA?zNl07dd@*YW&XLc1l+=Wh5ncUR~ab zcEn*E&fYd()drt@q)gca>;p&e#7!!rx6BTELD47m6K%hB@iQE@wlxHk zfYW18Q%31~XKOYyxCI=lk2$@ZIjDGSe<1m$U2|FJKKSl)gb&@nA zc4)CrbR(Yatb{`koD-jecTXM7wodNt-59wCv0V(cZK4((xsorU>)evN#zlgQW*R^g`ibj#zD zQuMNf?%%Ju!sd(#wF?Dqam|04#?&wZ+O|Z&#IXhuybG|n)ikHEIrcKoLs11a_b+(^>rGKmiM6D<^D-h(Dpui!`j>a%fz%|#!!IN z@aCS&meVz{El&CWBkY}{BaPZN-;T|Wom9}V&5oV!*tU(1?TYQB!j9dsZQHhu$vZRa zJM+ysYn`>~&!?WfOS|s9@9X+qD<40|isOe%W2O??$@Emh4ECNv{^m+6v~*6l7i)js zl+cON{`pqN&K8`MfmPq{N*cU48EGT0%CO@kmpYd}c}%3K#kVxWuf|(-ktm;J?4pcm zb}-d@h;L$?icS_pV`b%yBmUJ$*f4CH(Y{cGTDdo_V!}S84u_byC!04ok_S$IQ8K;w zODsriCQD{S491$`b9nQ02we?}1x_vFl9;Q;0rI3Wpvs`|VL<4WnbScbs*RQkeS)F zxI&p*1^PnxSMh?Grmt7w5ABQ}b|e$rJ7GcC;5-$q+YEi@y^of!dB?XSnbS|pClTJD zR_-ERdzj;u(VTB>?mexh?K_l%b*cbfA^1@1BvN=!<+qUYuy65B4X6-IS{YsVGBYo& zWSSM^nFI6+j5#9iy*Otgsl6I<@ujV*F?vslwzY#IRO7byl zQ4q*fNT&E7FWi3~N(6lmvSt7;OK0O8=Rdm-GJ%mi~X3p#F0uG2#D3 z`Hn&UuYv#POB<8;|EGNaht~feB>w*~v{G4{66mIMB<{a=QIG$NtN*_)zMcNsmQpmn za^q_JXdn9Tx40E>Z<16=`hQT~UvDE$k@J5B8~pEADwX|zwsE=Q|C=}Q`KV0`R5tK` zJ+QyLV5#>nh|B$|!I50)?1YZ zP}rChi)o*5nJN`q;_qigws2+_hi`f!}>lt-L5yt0V*-c|Q6&FbB~ zxzsPnX+Xw|*Iq4=wDC?jgyy~I-ZDdimaa*^0fSN?ww1ez4RGU~%E0=Tm#bT1^+0c@`7mj@24htA zyvP=Y7=R6_>Ld|s&dI+@Mo@(En+R$s&-pjNEIx~KA7fb{`KnZx`272efw3W3`l9_0 z=XMO#iB25+7vL3^k%-Ed)o)M7sNk1}woEG@F_b6ds(67`q`=W?IW-=R7H!S5XzYtf zqrmH7#pFx9fYn~bBr&+m(}H!-gF`zrxd56$LuTSqVys(qZg zEU4QHYWsW9c=dN4;qQcQ&%=itTFJX<`$iJ5E**&v#C!nmYb4=bl4RdjrY^Pli0zUR znPFES173UcOBdQMc~E5#Vj51*=)JP;E0Yw0lR>Ymrs0-(W)n&G1j=SJS$?=$B36g^ z&GCti0|{0_=Y7${ZT?=^ddInZ)dM)vPnVVm1CA7OBWhI11+?A5 z`~4I3k^s~IbTl!iCZ|_v&dm%Or&_-=s6lKpV}uo{^CI`g__xQ*LwF?0l>3RU1Nz{ZZdqHOHS~rK^4K_UUW9wUqTUeuYjqAC z0FQ9l>upir>q1DDC-$RjzLF>d*+*qe2m*g=tdMqtZr%h?_gXe`~P>s3M&P zsYk9zpKl=C(NUcc;iLX^abr|=!(|ZvDT%eO@sEf$)J;6I9|rYAfQ{w?_OBp@;J5}5 zDsY9gsHnoRH3kU9Cnr2rwr@I0w`bhtVT#(N8A))9^Dca;T-5 z2tYU7tz`I@z|qo0n0fcgh|lv4HO7&6i}Xt;N3%fL+Rd+bmH$w46iKgkxjV!7eX8m= zM*In7u8g%-U)l$2-ab18c4;O%!E|o7`dZ=hM~OuypD! zIM}RqJMt4>hgv$3mpgdff{aLbdK>HjX0MSi-*QIu>Y`JuhdWI2ovf(Qd*>rR)loLaR?_>e=+}PveuSyqpwVjS z-{uw@cui%uAgc`*6;H?(#J-gTT&{*MCsNQro>;r6Goqb8j#DeRaAIU5emXmTOa)bK zY=aAn=E5(@;y#T375Mr}QqKJcjVk#|jaluk;nAarDgrE~YS1wrBY&&?fMls$>pQo? zSvC7mK$vkb_TM!Z z!A^hQT?VfFCG=VQG6T6`-29=tX^HvU+9ATp>*ijvLe|BB+)Gv2pQohJ>~l`TK)4ho z#;-*()+f(_+`q*L^xDKIihdfUh(wn)T9+&f%fR*HH1uX=j|% zp5ik9k!-^s^&VMlKfNDVtZUpB zJ;B^F{-tNF6|9k^glc&Li(-R4*H9U=R}GfhZjaxnpvIv1PZdc#a^kkeZBXDWbM5=J zs(j&MNQ+NccuoYSO!C%hkg55V6!11CO!wOkda?d;kJu9>GX@eu{bVS1srGP<)vO7! ztL<`dMZzDz^1{Enn>iFw;TsIPwAOE%LM#fBk#(1rKn_kiSQtaR#wEJX6$5+{6H1GQ44XIILbq71AiIQy7YKFI8 zIKG#`l7D#~L*K?UyfHMIE&z`Ya(kex{6oLX>#zMS9`0&#xtvA0I7eYkOVmXmn`&p` zfaQz?&fl?4bQOu4K+l~}mEu^BXYdDKP7K1#_3g?&6ozEUeRbiuk#kIHr!xizE#vmj3Hd0OTH0xl)54vQW7RyD_r(tm zdAwjS1BWm-6!BbD($=>MbPvn=Cn&DTe|3Htx&#NAErGqenj|X?U_DtUfUz2e6Wl#G z*oCXggW!<7I)@KcJRcykXjo}jqhnxB{!p$ZgdN~nRVXfoZ>C%*Yr`^uN^!kwd>>*O z)G}y=FGnGqn$CX$8QqgSn-ZJt^LY7%-^`qIiI))a$oDx1Q_pcYM~XYScTMQAF^6*t z6vz2zPwYB|6O;Fweu~-_E>Xa9&C$&gb=&-wKQvR1*iuW1-A>`|t#i?NZ6 zo)%MP_f7gkCb|W1GEW#CJvdWx(PZy*xEDZ{q|7LxVJy#X$L35FrZ=hA1N_Q^MtwdB zjp`XF!2Xey(cGc)8KpcD@%D8n?csjK4q6tEeZLxD+Ugb&QGVj2-j=|`z?meXt-Sw%rNe6og7GgE&Y2oKO(QL#aOFI)xFsi zK>2T=8%R=`512DS*KY`l>G>9NzlpFlJW4iooVBOwpMS|-tl+n&HwZ0a8CAPG(XasN zx_|w#zgBJtP`GD`dh+?s;l+vu*j>rzU8Y-cM8+LdGboS2Zc{n#UhBf9e<|-Tot3CI zSku2#Ad0$7A?gkmv0@fjLg|3@!#PeG{K79YDVm%{nnW%xm?0w7RU>G zL=q9!2XAqC1LH->>A8@~LbHu;f`(gdt3FzEQV|7mAxxM2aYB5(cFSCE!eU)*__=!U z#iJ{3m*j6IQo@Jo)(a#sKM4%*pvc!MCa&JyayZLefUSjD3k zS7+DQ=NUlFn$N&yqsVuv{p3fXh1*XBJh zwoU}b9ymQ#BhbU2KV7p+XTAOp!Uo>{*WC1-UFcIL58X?srS-trSm~k zWP8I*nm*iRa7tWeW>ALA+BQvZotZB5chA`o7K(WTN*j1JLM<+NRsZDX2iW7;4cDTC zW|u-<#JPaFbp}YWev+L(d^42qjvLy7_4=-Z>)loBit<~W=+J|1fo_%tW4-UW4&TLK$dNmW3q9OG*mL#Sx zilM3RGjXl69`K)i8cg?a9U}ZhUm|S{ETl5xae&5p(H0lBrmJMU@1ui9b=Oz}_|Ai3{EJZ^ju}1JV2Z&jZ;nkC`XTW3JZh_owFn$&jg$mt$DTBI-Ex0!H z-0dcG)LEQgc5_x!^W-Kw#U3r2-U52xN|AO-_GR%$u~Qzjd0Y=0jWo>;yQ;SYtYBim ze`4NlGbb$@9G-<&4kW6j;KWC~QSla)^#V)b?_(f!a&tILS1x-iwz&E-SEA(}h|~ox zNo<3QccrYElxsDkcNcJ%2+ur6ohQ=^{#zCA1}?vJ;S<8oVK`LddU-r;+1y-&9BLm5Vf03 za!IIBB?wf>N}h;Iut`gmU%0n5KQ@26b$?&+0w@xmg>Th%=IiGW&K=v)Ke|An0 zv=LAeT;~aAoFz$?eBw(3d7K8Xlwp|DI=|3K++!g(CX|)JUh8_21BHjn)QU<5#=9{+ z5zd($oO^_M+jj6loa<$QY)qr)nksR5*-l!_`0U(bP z!wUmL@?;qBjmE9eoDclZ%gGHPp6k~g)d4ZiVJAH`cn})g2n^)*Zw5Jug?x7g;gn4rZ01T-2o=*r&2P=n z9%fkAtX~~HI0XA!F^cTVG&n%z``e~3BHgR;3kxK9r|`|i{*n^~iprycbF6Z?o$(g3X^1=rQX*BTjy;MKx!l^R1mACghc8-Av`Q5RgvFT%R2AsUsdLIL-{JyKukl z=8#!zY7(@ihsU>4J&`97+)*+5eHPHvzdxb8U4DKrempmI?o@#Yem>3Hcx+XDW#KXnbEeIsN_I2-$$7;@%}$woOOq zMOs;?mYXzt_}O@RtK-6aVRz(3{_9&(9*_OQzvwg0VJ@rE!8n<=m!LDVTs&(to&HXNl;vl0=OE%_vSXXVhGFq zqgS))#by0yd4^x%(i%8J;{!iG5!=H4MaiwSOkq9p9;oe^8mx9m@{N`Y8_8k9Ur6li zv}si1j^B~C79j10cr&}d6oKC(Rk%xZG_e7Y7roXDMvXy5-P$g=m6Qhs+o8T&z8{Ca zL)o7L%T89;T77nz5U+5W)yymYLsW%p4vn2n^JJed$SYoD5l14fl$_tS9z%AIS83|m z8uFrNsqwH6mLk%yb$+G?u;4%0w)vNcc-EMFfN6JPA_}_ksv{dr4i6MB8GVoLzdh4r z_mPncOOopg{Qh(&kd@9TQGK4hR9q2-+h8`}-s-_i#P$_PB947db0vy7h>S(!HTJHEjH zg8=UuU^IWzr&eYnS~yWq8z=B5W?t-TSAwhOz}aec^X2y4JzY4lXe^v-k!&oq%9k-C zn}REzu3?gqUfkYpb3TQvE&ywqd+@Dj;vCJqmquk0jxUxv zI1^WNfiKdQo`ydf)&sSSl!sN3_O+`$+;C>}W^P}wG&ik&B}#iY_a$7a1&^E}DiqMm53Ltfz3guci}l^tPjoX^ z+_){>N%C+!k}2|7)}_XkkW1Eke11Nx@c_$5r`$K_WaY;kgOARJatfstc9K!yZeVZm z?lnyl^tfDwdF5Dt0X|iY)Fv$S9A^{;kJB!01g+N8+Eh@{iX}#o_)hI)aa1XbHInMC zV_)d`AH5emYKcXoO0s7KO$E6m{%UWOXzIGW2$q{$YuUqJI`zIq?a@q9`vpZv^vPgH zPFTP5nAtYf^np=|ljmoGXkPIr1NTg8OZBLAl!}>5LfCaW$bchNNN^}$t0om+^6T^B z%rh$I48}H0^Bk%F6d#LZMlTS{H2pcof31B=tDg$ik*SNyN_IxJmq}Nv;^n=cAG}I> zv(qNay^}_kZ)I!EFl)tra!aA8bMtv3+c28ZATE2GwA$@%BV>PC?7t~sJQFi=DWLW_ zoI(0SVU6(z#Qr$Y`hjfFv2TGMut*Xm66Z!BLG6yk<^u^wd z7Z_x=Z*G_=Y695cRLx`hfOkz#6V@X~xGW$~LI#wIdp)@ZZx^^J-a8H4=c7o`m{1d1 z$5?j9a5q&OmeDwe(scJ7vFUOqZ#TBH*mQyNXzE}XKX0)4)MfpT(nHP5eM1s>UX~J` zzJH{*ASAyV=TgeB2)_3XH&=QHu_!o5FQR&0euQ;`&H_|WE{hb;t3nBxZJuPrI5v!D zC_#=>frcBv3DVAP_(Ig4W*%{#4MI1b3K{Md#y&jfT%TA}PbWnKfx;4^R;u`|k% zsDhi2E!v-wB1Vbqt}rHm+a1THn1#NGIx+K;h%bct_RptF0@qOkjzDs@I)@dzm&;rp=|Nc0 ziM~s7d4z<90XlMNoh;2v%E=0*cGSrr7zV~kk3ERex?G-={}9vm-;uXFW2kx-u5m8= z1W~J9Ti0zJ?KX=BaYu1ouq2IzIk3J5s0C$CH3c z4Zm`RJG|PpN(HMSW{1|;hh$4*TAjd2#*5LGRNzR-{hJ$CveK6G_b|Y6Sg2l9h?Lb$Q$4pD z&8x6M;uDBq%Jfy}>FBLs4hC$J+zv1_|JFlT4Lw0!v3J+$-7P+bO}B%XB`s(8s#zTS zbU)38WSc|ip(&XNt^CryVrp}|P!;5(1pv(ii46XDD-2*rlS)+b1L)7tl(<<-kmkf;ABmTFLuB5GmYOnUu;OY(g&=96qC+*>Vlb zSO9KLm#n`}fESr7G4v-s>YL?j)V5NdfFu;LX7d3JdM@2u)tI1Lw z6(Wif5sBrj7gvm0KIOx{g-Wi2n3Y|KzdH zKC{S&gP0rv0*dpDC)>}gbU19O4q_GiqEGDe)jclW&#&LM-X~#_2o$*u^`)3H5vc_pN!(pSN?``7YmWf?*%6w@~o#rpLrwF;1wCccjR^i1R^FG}zF*$+30 zMIhn4W5fsl&`Sv+BH%%T!k_G7J#?D$3_P{J5k@E4Yz_R2psmlFIc`!LZNXY&D)J58 zXa}|40Se3F+Qujfo$dAdZ~{ZgojXBbahKbhCqa46Rm{!OlOTJoGc>Hdwc2Q!q4In( zeivSL=wegYqG)JTQW>p& zUJw8JSl5D%)|hse27-05Z8J4kA2^i^i~2cLl%y`ZYeM@MCLxYaB;^FJ90i3_SLd-R zYiGXSI%>W!7tUK1dzr>+(fg;)M1U&=S>imqMx3#!;cMOx(C<~dO1d^}DI#R-aoPSKbR`$09zYcX4?ObQAa`BH6Veu0vnTCp&>ts{{1jy1?8X~Mx zvN8oGkzeal`kbm?)#+H`<5B92yT`l3fXVQ0GyVAkBlXpl@T#D`&~!!q zoi`1;+z6h{l5^E_Cx}TLtBm1OS;yA2F!@kCgYt`%k`*XM0_mpc&HmlGyD6v zd$Gf@9D~1%Qm0}o%LzS|8ed`i&~IqtUm*|B+F#y&cvqH}MYM%WN)X;>;&slDy|s!3 z<69ffHd5q0OUvM8U~{WqIrreLJH+8o5F1$*-IvXOuX{kx>~O|L=?Snm<$k^jhC#J| zCCwfP?VcP*Q?18d%+wd@*vFcg(cAV&6_iKtiaggITO`?JkN= z#wz*+C!@%3{0WbUK#$;_*cmD_f|VK)agjn+j42Op%5*Ho+1nIRL}OFo0MzU;ew7!v z=#_n2HaZBkK=_YXO>kT-7FPC3OH%;U4@?4>S~e?QrAMU` zB8&%)rc>mV|O}pscjLgTY0kkff2H+K}%*HKdP*IUfnLIV&73oN451?ejRao z7YPsEu|#Mhpz3pEDe!~~MXU^V$}ZYRQI279lGZ#B>RLBkkQj)Xlw7pV+mIVUWPN~c zVcZm%cPydbE8ysdU$)I}#Zg6F-*nu?Ny)8#toN ztX>o4`|hshXx5={>!jkldDNgfB<|lLq#3^m%i)?Z znTJ|@(P&MQa`|Nu{su|Wh{GQfi2kvFn&DXLi3NZ}FG2Bnj`dp|EO=?d*fo}A=*}Tl zO58pFIo2tn>pjoZ+zZa>Qsq@^KA!9@;&=P4t`Wu?S;2E>$P=>=tkOEsqFmMGYFw&d z*2`D}TS%=|GrvTT{QYj*xCPqZLZu{X32LzhgB)c(dA$ta_O{JXl*Z^i2+-1&TAFw=h<*nlmS*MSst;yW(&cJdjg8ctirAChw|h5^X@A z2OJtZ*uC!QlbphJ9yJa9^Tgz(Gt(b1h8WAN0rL-k9zxncK%uGHbyG56nkS^P?d&cT9`C`|BY)>GT{*(b%fFO=N5|^CfSJ6^OgG-L?g0~q(`RzY6n8~xg?ox z(dJ-uVWh*;h*2AmC)fo3g!t)1ki0KmIcjT<*l7zGi`z5r#n}^|Lt>kOeP%x4L_R-0 z=LQd%HPh58b`)>%CF6K5@eWj-; zu?{}?d_`sUopnuoekGvLu$3eE^PSi6?iTyB^U$DX9Bt5(Sb{<&YzTg|hP?>me4>)% zLxL2|3HG8!F&h4;Bn;J3wKb-LBS8zGRMZ5WJ58Qk#aE6?^HM~T+~%d}Nq>Tk9%p*R zrm8cHmUVx`fKG|sFX^jzY4gG!4%nQ%%(O-(p^Jc;*0skQ^v%1cYELLL$}a1%FNfnJ zcm({QBg$LFHaZ}0Ac|G?;(eRrEq$@-o&P`;7QQ5L59HJa-EW`R!{%+ng)LnKY9y!^ zQQOqBr)-VQLz3(8h3#8v*4r79&oi$f1G^e1SPcZFn~4mDx@Elu$@>!gnKhg+mBf#PI&}a~U3B-Tjt4O(hB&)(%Jy<04id zOR@}%$KHh}Xtn8*#2SV+1CHEI~OU8cO z+Tb9Bv!SBzd^#~(u9AqtjV!g{XF_}OG@RUkG3TYw3{Jq&Chb-QNY3dO0`kD9{Rv&e z?wY}qfn~rL7T} zHKh2FI!L=S{C0zZcw@aUPPx1iwQQ6SIp3w`WBUXiTS<-lBD2ud;cnzPNHYr zifd2y=bPk%7{~U#Ozb~$XDZAj{?%vHfRagD8*RTdeq1H6mez!bku0%&Xc+O`2cU_4 zim6c5c^DaoMqu-^4BNyu_t*hRRAox>Q&tZu4O+l?sU zAtF7x88-jOe#=ZQ>`2e<53U_^h}&&8EO}!=Fau*jT4n`}A)hxgRGEi5O^FyzMswN2 zKeMU2t^Q88q677vxdf)6h9lQT991yU-Cy7Gkgn72X4!``F3l3e5HL_(ZsvLZzUa+; zp1QIb+`HD0YU#)F1kA810By*Gbmqmf1#J<0*Wq!;r+f}|R}1nsaW?G$Sq-~lKHy*P zL22$e`kQr04D;X)Cg&B`U+{eeLwnOk{&Ew#8uxv<3p?Q47z{8BCmJF{;)Lv^+Yi8^EO)5t}0DhbaTjqS{Hn%lOLH`VUykCZ` zR^2DGI4g$TP5W6=GWydm3H~!OjEsDqMC{0lu)A&r8dS~{qMHIHh*AcjLOsM6k;qBm z?y`aXMQKfjhuQ_&rd%D}JM~>G2O7Nw#(fmwHB^z+D8%THj3+4J=ld5@u}m-BKosA) zvDZhf-fh) z{X-mtJ) zCc*@Hasit?>zVkC`k#9PO?96)_LKC~3BlZo9n81$23`g-Q0-V$BY>^}M~?pTs$-wm zYjG}uxWi5)_>*P~rvTn)9x2T04Vw9{X}3WH79GZql8z@V6Kd>gMGfqh0A8)H1$L?M z$gL|f&K+C%G36J7&!l*EU!TD+5JZi3Y#E9|tY}f3+hCA*z!n@qb0ryBOa9|#nlboT2ZqfL4RbhJp=gmU^*wn}H}rIeXjKr&Lm zcU-fD9=X{3jhvHe?r4}QU6P?$MAP2l-mnAzZc!&|fk$v|H+jRFD4Lk3FKDZnV!UwGl!lYefdAj^tWnX-Ts#rQLl zjLwR)w)8+oIm=w4T0s#A_}a4`$bV}f2(O&@Ob~J`ddZZ#RT^bayMpWC```7Az z--w}NQs0=eSkSSB6#u}`Urv2eJ@*zZNkhHjhGR3(pv4Tk{twdDav`<#34N!h$68S106mN_Dz-gXs}5GT8+8I)H--O1J~?Le@f*eBj~`{b2V0Ll zPSqVaRU+~R?Ukg?qNmuQF>%VETfQ7C_eAQBN;Tz0Xnr4^&O{KNh5Y%=;mON&C~-@< zj9n~cW_c4S$r&zuE$cP;9z4*)vqwc7N%MV4R~Aut&{(xZ*7Z^NFwuf(`SCJBzt4ylKm33WhkF4F|$X**2HewM7QBt{-mW>xcmx z>8nUmg~CQH`qYyLfiX$)8=>d}75*n+)-ZDt}_8Y074-|_EZ^4iu+BYGEr4n;rKe*S`?X{(TmaTbDLjfE`3;d=V`Xk!tgC=05$4G- zM5rT%?oP@~0Z?>HH^yEM$a+pwjlxOA2HvEb#x;JTd|V3ph${U=d?-2XH9bJuOKBI& zO+e21aU!dT8>1L}|7M@)LUYL9nMpjAs<2;T-Ye?^u>((YZGCB->Nt1@%3~kF@D!2$ zP1KH3H*mEH8)t(vG_4Nr%uyYDKU7L=x@C{;dD*#AOPd-YnY2*5 zrIQ>1xjRLq$*wQg`K z-n0O+_NEu0#JXI zAmcbazCRn~@K)BhX@)QekBO}|Bh7>qjb{ouF*sNom6VYW>ZHoS$nUgLXzUItfO9AC zhRa}!(x*=bX@;IoTQt|nl$;U8Dc3S7-fhJxAG+2jh!FU&$4jFfoXFTySAKIDClnoj zbramuF(D-)PzPU>qA`@BA++GN=5d5U0WcUwHiG4V;)Ad6xWC$+W@}ROYSFDSG~_P= zhHkI=rcD;_eBdWfgOW59j0BxOfFPEj*EgAFH%_!XeD}fIGtB2-Og&LjCsVk&u&fT` zNgvSKM&$GI;$H_C#^gz_)S}GsBe#=6iZ#~%g1KCF!Lq5uwr6fA)b+QJH~YWD&~oB) z3}2KhhcsEAluMF}B`jOSKf;PSS0`{%h2Xrs(any5amu4xMLZC?2#h2Qbl_@-tz99W z(U5d*;{@nh3^0d4~peje;8Fp?#QDOQPB&-qT*XOJxjgh-Yj%c-@RSK+B|FIYH@1_#DfNMjeGN#A@Hk_J8Nvc^~`)N||p&S7P zU*MsF-maJ;VkP~3z^(hrs`pjA9$wk|UM{l`@`+A68+sgCRv>@!bAeE-6edn&7+|$} z+J*v|uwBmSi3OQ^sVi~k2j+*m=qB)BIR=>OL0X-2!BSe1`8m4V!4Zs+F$rb5#PRc@j1OZ?)1$+^;~XT`JS&mL3C3G9do_L>>)?`5@U}~ z)E>NasUk;qp-zmx!diEx>ECXC5$Ar>d8U`Nc>!B)wQi0?Qh{blBc76GdFaLD!sUmG zw|FDc;I-k^^I4JrYm|p^c;6!}PsH|eiL9C=k;wG?nX$j;ZBLZekxRPUzaItQqV1L7>Ia)1XLC8`2y;$)}VusP;ByE$MKgB9`r10=lecP>six<0v zpR9CAY*YqK<(-H-)T}owfL1=ICK65c%ckymmM8LJ4kSw}a~l#k8_1ehGt$$>kzf&{ zcdye50>N*>Ms7!B3P3CIIhFRKv0>eX)JYe;OB)%dsRtp4kY_>~JE zmrBiG;pun^wUKxw@l?ih$J!ceV`B-9QG?waNj0=#o1sr&Saf89yBd8BX%P?~ai(Gm zR!QF0??u^qZyP%anAI-4bAGnM;c^(b;~R3>ec-*x;Tvq$-A zRM%286}+OHy(4_Fb6afY^)J$J_4MQ~B5fdln7nVq6(+EnsQ(r}L@BHK^F*nwtp+BW zC7mhmc3^o3+ugw%yA6}AS|MUoJRy5$qnpA$D<6_Q8Le>nr_g;WXy+{U*~0@*q*g?;RhvMb zMBpA5y>a|}$A|iNwNXZ$!<8nq?s~hwjwxstl_us)IKf&wl0&rYefUGxmGtZaKu(ma zuMqM!K!Q0or2&*Nh>7*_plaK%*QflmcKH>X!@`uCd)d{GVGK#o&BCq&zm!@rbKvYp zc~*f!#=}f_-RjMxr5S!V9guKryCyGr&Mn3M3!gzg6b+5ge^ln6TPEpH&orH(r+d~x z@>Vba^K2;NOaI-L91x2AsK`_$_nr@Km1WY5niv9;@@N>B&+`eDjEG2G!tE}Ai*nR7 z%6`Y=jD@7%7od}Q(XEsD!A1Gavx3e*+RC!H#+*?COMG_C;mHeKv5IJb1xd%euEkvL zqC_TS#HqL2N%r7}s^y7@TCRdG(vsFM-+mC;_0ylRoXYN3equ}QUlfDuQuBa#%UA}} z79tVjJwiyjVv@8orNB3t$<+^D2q+x7gyWa<)-ax}NbZ{vww(EG{aQ3Ny8$w57(i)a z^KMq?q$W|ZEp(-n(_QAn-V}tUuaFJr8So`ZAs;794 zjoq`M*^g;J+!tDh3!doqPlxhFRzC)QBR!bCY^VQ0(_2Ti)i&M3Efk6rDDG~-i@Q4% zNN6Z7MG8fVyA>$z?ruSfyBCVPySoI}V87ho^SyuMtd(TtoRv%Fnwh<4&$|hN`}b>- z|NLgp^LVpwqVpHO>$Ot^E*CfoEAnbl{`}dGtt9kNnSz*%+HG?-(J#{*ejO?&a#$;; zrjY$s$F5;Dpld^bB%Jp=OU{bksui{V*@JAW1gZ?a=9Io}S|6t_@nHF@&W&w+z_j3gPk*!JiNN1Wl zeVYD`^>bw_7F@ggVWZnrY75)b8keAKf<*ba=A7&VFQTUXBNWLwm_^za&`Z2Rwk!U4 zU@z`Ni44-0=l}BY&#d!*F=Sqg-pTFdl261eeTP1?sWR)=shvtglAg$oLwlTW)UF?g z$n#C_o}R+!@P|__1cEk5shb)c*!36zcChn4Flj}>)o40r$UDO1>Ka-xDCY#6{vSw=%ceQ;b5nsKM$phjk z3{uRu0&jGUfio)+n(5{4q-r`u<$L|Pd-@nzSqpw_Yr^*1KD-?qtpR_yo*K#2YpQfU z1|PZ3($(VXDl+^sT}$pWF0(do06R~xo?7|6UWva3*5KAQe~7u=_ukoT;v#nbGwbGL zf%tWa{qKu%Admxa!-c*t>uDxrJi5^X4e*BIah^tnou=!CO`narn`W;Yr3Nk^EmhvSj5n zSPafg>G0XoJ~LF1jHcQPG-^?82S*GMv@J<;MHG-&N706=Sa^~|Jg*fTZy`1%0)H0` znKxO>swy^N-2;!0R7;~J68#f<-)HAFw4e|dV;L1Eb2R)x>sy9-n3gyCue~|Sewp4B z5GQ-!#Q>;a_C4?(S5KPI^Np_iKi9h5S(Jb#+EzHQMrB>?J#Ix<8G7^-w2b&j4Tf|I zxM>eE?3t^{X2mbz})=ZDmzy+kK9w6aEVMVXHB?pi5Nm8 zwwzYS>)S9i$BXk5&`D+^W-EWthPxzn96#D=3C-e9MP_1)$WMQ#%pUEGP#59aE7k@+ z?;7)Jfk}x`R%0hkl*?KvMmHtq0C!o(eoxLMt#fF<6N5-XU9O6wd}80C&7n=(*jRUu zqrBa~9g6=JHz)zye^qFS+|c|ORk61NX#B6HaE;0(k$j|?wK-~EPd_~1oVoGCgCccP z9lV;%6J|WzcC&D5IvhK}{JfAHlwt9CesiKjqCkvAZV?{WJ9C{`8=lJN?65YXm?U6wb^YmxEUL-ti(C>OsS*}{N5p1Yz{ndJ??MCX1R#sFS z!JP!%kD}$zqG4ep(44F2We%oaXglIM74?d06>j^+-@r(hh)~y0Vp0w7<)ha~u4~TG zJ(LQsH&9^B5>x7jnz5&F<^{i)=OB}vU~Z~eT5mk*j=rY4|dbMih+Q0R1)#&DM%%%EAFZvHUpYgK4hE{nIqW2Qs-G3D(-Q>^KYDQH})KJ&|t z1tpai-f-NYs&i!JU95%WKT5@VoQZWeR(ZzU(`is@ERfhpNtnVZ8}YBJO&G%Mn%~<0 zAhPzC`eYQOabdJ@sV+3};}Dl%#oF!BsalH#c2zlvs6})K3aY03eX@&IWOgx~#k6t_ zq!rzLyQP-d1)X>T!9ZMHRoYM2)LHa0TmH##!Ws*_{^qrp z4*wyrnn0_qo017@eal9Q=&A8%zsni#0-r1WoX)cIK`kR0R=gr*=NAD**t@`@?H^L_ z;9)tnchRMD(S^tPJEr6(JEucTGI64yN7PW1)fSXm1||oS7QeZ^hzLzLGB_ewlJo zJ&S+YG4||kqa|$v$5Pj9Jy1WJR_D6VyvzKQolK`4cX`!&_tD5Vi2m^3zae1V20=y!5~CW zDfQ}`ZX7X|%FUMmez)mU-8qL2v(XP7wudt=FCv8vS9>MEW%~Q%t$aFs;nQM~%N~i0 zT$VT`jbpl+w6*QWhO>(vVdUG_GoZ^-{xcw=Zzk{@-6JgSh`?)pC1R<(f908&Tgc22 z(rxmC^EP;h)2KP?GtyX5WOc^SV{0T!!0T-kFaU8 z0Ha>@-$&1i$OkLwbGllRQH&dio5TD4%t#?$mzrpVa+_N!z%8#*S=xEj_4m6s1t&K_ zsKa3gm4Uswv={fX#CJ^RNk{5C6MYv20rvaj;mRt34Vz4DtUgibLDMn{%(oc`4i88I zj-ur5fzB2`t>sK)iNh$0H-g?D{zFiVo7^+yk}*Bq4ib)!4YKsLVO_M~wS?P59| zvgC~TV|-|0V!}VE)M2Oi-&T3PnA5+H!Y2kLh3_e7xV^+h`cM+N|HTZU{_9EJwSbgJ z4h8@+GJi}$C*?tK)ZkzDDi$ci(EsVZtG(yG&`5gewVxf>Bm5*Zn=bTmfAO@`(hBr)VN|Rk;H~QK^ zE1sZpN3K%~@m4F#K2EwqhOTiXLRcp3E5TB}P1QF;A}#}XV~7gW$lX9Ua{OQUQ3Q3r zs(xt%`Fp%4>&T2wL&knAsgyD3B4~=(Uv)3$9C?HORZp5Z@(0^!dO4Br2|xddK-};r z3H+qFemj090&}<|E~U8XU9rFeHND{5eJI95Ke4d1O$Yw{G3(;SH%~?pZE+$4~luPZXF9*1m_jn=Q2CBx^Aphu^Tf`bvv=`X#T0039IpfD* zAW^nIvLta(hGfBnz-S)~X((yyHRxeVP6hl*d|NC>rD}bfPNILH0oLWt3jPeUoBs51dV=6Jac6~up> zH304jAsdn5oQ~af6!DE8m=QHT^;qyeRTNRR<{(Gb^8&one#fYY+3oI?vN|y=7|r&j z@AxjbB~o$UGDI~mbU6WNe-ptQWCcH+Zk$fukpD|tQp)JW4v)cLtqoUG$CwJr-^Tup z^nNoT9%-ZnCiR8#Hn2QUz$f(t0tu>}6>d#Z-w@Ay{9Kz$^GGsH-ZoMqWAg)Ph)Wq5 z`$}-3E3*0N)&yR8dsEHr0{UeiOgzJQadJp!iujq&z0 zb6PT4-z zm1A7B?*xoJ9d4FUy~mL{?wXAvhe^J&uQ62FY*3^mTS~|{!34b3S6JDOZY|Yni z3Z06x%V^kBugU25Po`p4k&QLp`N0`d3+S)P{C0DXw0CJY^pBI*f2+|4P0bS_92ENY_nW1o0ZigrgNSPtkXTmFJ8d_tsEgCV3qLU=DnZXXQ@?Wj2@!wMWF|}%A zmtG(r6K8JcW9RK7md1t!l4ynR`ZjwS?L5~RM$(H^eND1U3eGkW_0S8vZ9r^kmOn5&$HerotxWBWj*YMuXh9JeW9DxeY~Z=#KDu+kCX&6o5Em*$+8 zVbN$e38js?Q~d>iCvRikC3DVY4o}tyLVIUx@g}}h{p7FNb|pW6urSAJY6dj5EKGtM z1J3VSLvVj$(#U^7EoV3XN{BSEI#VcYkDOH10?{|yZEMM@ZLuz?pl<8XuD?Y)6*Kal z{=Ju`geMx&GkT-ENnM73!Q}G6MGTYjgTzGEXiDWjM%ze>&uRw!vp34C_*HRd!VfxzOuE@hlZPhB0!9H-#xbr(MZvigt zw|i;m_B)>iPW1!>{uD7x?Dxqe4ZmBfVSI<$XuIA0v`FSRt4*0T@|6J>r+aWAEHSm0 zB{)8nz9>WdBh0dU(NdWGUcbLr<0OSH9nt!9tjAB9|D%^1VN!{ebim@#yYJ$~gFb7Q zHuWDlq*9)>VxP~4X?1U?blY8E2=D-=8x~-)H?rrxTlJvPH^j8REK)1|{VL#Ez_Vk^ zwNU<+HKiD%?ri_C=R`ZJb^Gj`npzk72SK-HcAD|oUUuN?qR_@^P5c#GbB1k+Y;UdU z@1OMRQZ56_R?Z`tF4$}>}}EnW!$l`dw(@cj=6rjhI1pWeV<@8}H8lX>sX)t%wr zNFbp4V0*}@Kc9q8)BpDl{*k}n2OyLY3%XhQiLC7Hw!QHu9efYzo!mKDm)B1GfEhR%h{HJckBXNWkB}f_ zxaDEIhtlIS{4xIGAAI!VO6N@v?$QAN-+zH$o=T`9iQ}Pm8%B7STrB6wEkaLP=g6rq zjCKn%9~%%ELmx)R19?vHzHgokqKJqz5rmr+^+=Rwz=z`eF;L{YC5NQM05=bBNB`@~ zc>qdBzc0RsJnXcPryt+P)p5nB3Rxt=+s?Y0!zC@H%jX=g+Tm3j*u}b92#?Uh`q=xf zZ6DMw?C!Yk2VP$K!0?f{zmsUZqgzYndK`4Z@Tg#Q@*eG1ECq!DNtj+}wBRsT;k;?v zgZS(F)c>22|4oZ8KumhC@i|7=1qW>h4_I(cjb_F`ZL1}2>MdJiJRGxehs0Op9n1aQ z_|8NK%vA*cwd8x6$Gd)GUfz`SbXQbq3~vb5SyMkyN?q}PQ~iHq^uL)uFWYLQE@|5K zflq>eb^u4rVi{pg2TX6IR$NdlzrmMf<#s&g0ps~GokMu|d07JH^$a~zp#0x-!&k$4 z(D)DW^Bewwr?6X&>`-_1|Nr*lh@I4mv$et{baaXD$nVKg+wp-l-`)?6$WdRR+jIGT zoj|UvqzTisl}bG%S~>?AUXuhN+&M$8r)0iX`r>n56VI-qYd^oI@;o;(#Ucqg@$rti zp+98gML??6%%A##zNl^(`tawS=zH3y`)3h_iE;Ir@$QmS{Xs8gzhREzbq+Rp|8;ST z^3Ep$ZA6QR;19WipI@O@Y)En4F7$6AU+3Eg*-*AXBo;Fo&hc-7rgqxa!r&UJe6?j&qZzy3$*1LQI01tNXAri>h#DZua<2ld z^L}N#?ffgYYw~;*-mg5vXOWq3)Xn$b7Rbip(_FsG3vlyk;(*E$|7BA(@PBoCa_N8e zVOPBATZIF%T& z04KCPfV~3CbJ-!oV^TerB?ZAL_9kzM%HnTwsUWZjpZ>mBn@>W1H-&hHQA0Qa{Xds) zLN^eku)TcLE7c$wZZg;SBzXHUufMGJx|U5K;#JO$3C^0a+Yz>rvpGSm z+uO;5LTPzcMMBhXz7=#ze1CVDsmKt-aTR3xGbW-Bg*n=Zz`9h}Z!xcG?0Q`c95u}_ zQ=c3!xl6W@*mLT8ANFLl{!+mBoJ3m%!RjQ~_f<%``uiL^o9^!?-lOZ7gfyYM(QnbF z{c9>1rp6=j;15fGP+{nYh5Qj+zYHaBxH#&4;(zY&COde2kM>6mtoD@>=52V1tG8Ib zo(hetd`)t?uZLZRzYKtGm^^v|ly4LF>kw}yu_3zVp>4VP zqlboXt)noK15DKGdHJLMi1Y+a`drYxk0M!8o*6o_*$svBlewrC0kvsFmLn zb{%_2{A06`=l3~r%NgDd0eZT%1c*7Bl6B}B#Uxv zYlLEQ+ZRZH1Y$Y^1Gt^0?OH#pZl`WcK3kxC7Euqql?q_YH`)`L`Mn~o{GDORKulsg zkQ~4pC#lL|-x<3hPTolwr}QzVbX0ovn52$uf&g@jZhFivbYhKKb-nz`V}$_X*g<>z?iMUw z;Ys)5kgB|wO@D)q5r9nsr8yT~dE962_*Bk7e{~<=tt`im)*pEr84?}zu!*lSyZ`iQ zQWa;AB{$?=p~9$vR%7{x+6059x%8U|%p;5!{%bCoV)&HQB^{^XWYUH_ZOnbG_w@t& zA*7bm5{O`icnW;{`-+yh;3bbX+duuS?}dQ^AqYK{FsgY=a?2j|$u#-f9@swSp*7p{ zmGes)f|QBjj5vqHa;Ecu`AtatF}BZpI((ayTC=^J(+d_JL;YYJZZbw;~X8FLZpxX)r5Jtb}C#IUBJx!KBG6I<%g=6Uso z_h-Cf6wEtt1wzW=D|dnE>J&zkRX3;OJ74?-=^{lq=E@d zXn5+#v;*+SbGFQ zoop`o@aJL~#N$Wl<-PV^aJn7nct%~xE-AJBrdTxei8`q+1&EVJ_Wk+;h$EJ35XN&SAO%s91}qio3=rR2wF9VIUxWA( zO^T`3-3Ur6^e@1IZxI%vP zVH;wnE*F(Rl)BFCV-**66Fn^i&K8uiibHegy%{$m`ErH`!NQPSjYprX}PWilOFq3dVrBZnxO5bJ+ z%o7uT_8hf*rJ6BZh~hHG#XCCA2g+l(o4ovb*Z%Gm=SdDun(&5Yg7fP^EyqG>m&4~3 zCB!y)bhCupAd(pWpzUFh@eqP}nut9fUc&oaC%o+QhZE8rb~Jh2S;v`57m3_z#Gy*@ z{?@He2`bp{z<5p(xm~8o;%N@b7ZtgFM^n26-q0Sa}Nf-w0Sy*^PQ0le3_BCC^D)`>XaFcpeFaQU!-{QAcE%z za$7oxgX5GdnFdE+`o72Aq?^h;Uq&rNw!KXV}!#X^AJ zxxizDhP2CGOw5F>c>@xJfi71daurF}J=uhaop?mUJLxPnDjjrgU}x42t9f3xIHFCe zA3$>>nRepX@mrO9e9hgiT1SeBvCt)2?h!%>m%IU6eG(9BPzdXDEUR;>J1)@IZUZ=@Phv6uEX&Qll)Kk=ekMgj0iXTS6BSzswV@2vYi@>kKAcF$9o6t(_C-^rN>WC7s{O4& z4y@gl8U9u<5o144yB=Ot915}jDsehMO{WZVw)b&s!r_&f*_$$mxZ6WmkXA5pXK|6v!NxJ#xk-+wkdAM zu11@60|gClWE<-Af}bIWc>|7EBoqa?-;yydkuRYq0b)DmF7=I}M|eTLFApqk$F;P4=nYNHpZSI^)VKbMK2fRG?pqQ zq=tJ?9!ygy3Q=oT6I!OW{j^BKV!lAP(|RJ;(~iO{uThY}5zDKNR2W>57g4o?>o*Qq~WXw9?ih|9cx*Ju21&o)1o~d+2Z_iLu&I<)FCVbnN7~yEMX;tjJMxh8`~P@;rK+i8dN79`K+Zw|;&s*Rq21vcKA0(#+?m z?VuEDUE$!Vzh;V z-C>tw{`)K2-!x%uKiCZgB1@6>2H*sN)bP|)J++~4T-Sy_G0`59T*PltckN!}t17=; zrMxT;zMfC6rd=|LeeutZT>o7#MR8L?H=8`5?@p@Hh?P4sL!rTOGzpVr!1rTQg1#~! zPKf`WgMFSkxZ=NGhvZ%UVAJ7Pph`DWnnZ}>9ieb`=e1ZX>gMhk?~pnTGvYz|qEDa- z@zDvKdlnBa$b&tq%Eg`$^U#UQ>3(~wll?E(r5(wk@b^uDgk1)GDK6Y%NfGw;8*a7A zyj7GnyH=Op4Zx`*fZ%b@&mK46A* zGM_5sIcSJPjMVwM+&=Jw;JaH=*fJG5Se!%Vik%#d#KIVEa{Q&@Vxcj_pSi}-6y$Wh z=*!Z%6*T$v8vuPdPPyI7RzZGDd=@^in{>oMwen_VtB~V4> zp1?O06_od2BgWBsxF%iqH8!f7Zq^*{Nv%fC8BB9Dua$RwFI^{#8Vk;p=se^IpF#(# zUNhifp3&Sty1a4to+W#nZU=^7-q&527i{B^f`j{We`c~B#)l=@O%-Prz!0~MmZI~Xk^*g;DD1}6nh{&~M1qK4 zacneh?qBrL*XA`YxuP5>^4H8sMj6p~h!yc=JlYiHucjOI_7tsjP!3HdoQpKiatEA+ z1B-$ifw`g?2;VrlD1TiO{|FhX;^8?>{p)MegF?jumMx0GIG`!!U}MPZj3)`wyTi3m zYWG9S#C~dq2g1iukT4aA$X1?u>4->IgveDC`h?6V*)JP%cxY=wK5OWUQ{XYEr^O(P zgf0j(Qu9-(cZ3Bd%95(Hii4B}n6*^Y^am}Q4&macmLphBeD}stm-~i$xQRKR)905E zrN*h3n7os0oZ5o~L!{}9g4{yqrhSn-_XaiiQ5l7y@x9KffD}Bjk`vufFz1cXr0e8L- zM+kyH7rAeSr;%P*_{2*$q(sgYq(cl$>6{YkN#l;fB?oZHP))C-?C9)j6dLgNbv1q% zoXC4yljy8<wO+fPEURr*A}>}62l*H^yq&S z&zN1C>7?ZKD`~`9r^jMl(n(MvY#HneQJ{%bT-WY^Pdj`uX0 zCIrkJvU!5(M>LeP85?R=jrf-->o!;-30c zX6}XZj1We1#~*1l6ZbCb-_=b~Y-|*5lE+Al7x#alWH!&sA-gPlawtXGt#XGU zU5W4XH^iPbEpPV28$#rL=X?+%ICEwWK9qi80s%3v#0_I#ze*1-#X~w7&*3t8G~=T zDFhzYB!=e{vcnlof{qycY`+d<$*#oi&}fH)T0bM~8$VGLNCtLFLb6c}}Wm3(Hn8HQu?CLp7tU<5Oy zoidVKQ$yCJAuQkx)YCV9}?8`K;~t3knd5QN+%$^M;6x9}zo zLR&2Xw93W{xV?3d;h6oNN=f$nrSIsJo4AlLD+g*OP)fLvGqLj4OxS-?Cy65gt`>An zg_Mw`TwpYu#OKxnsuTZX6?ar7Hqam#QN(`-vgv21B6L?(rMDVT*s+M4{y@j*>dA|S z9FE%1do*$``0PWXG`1qy<0HHiMHIN@efL{|d9K19u!J8z2RG2X^9X@Q9y|Y3^ABfn zXC46;H7v-V3;9GPj;BANu|q+ohY^>Vk`_z@c+@3pc~C{FQUame5LodOkVvP7B4ZuVg!Cd~AL>?Hj71IVhbb)zGf#ii-(TFVO2%cZmb_;-> zdn8VVn6;}fJLUu#GSV|l305&SPM4{yA)NBNkqhE)6>o@>6*bx(gh;x_^SV8R9FUbK z^*&zuMT~$9f2AE*%>3b#IHkhwWC*KJC><{$GvmMHPX^XQsg6`_Bo^D8AMlBNAH^xVs%fBdwfJAs86cgfhdPKe|H2WU4kL-P^3dvnKMp zCUp+rkfHY1h=*jGBftAI$>K<~KpnaMi^lPPcQ+m-vYXZGU%_*ZP^6j{`>Sxg)ca-kd)*p6yCk zg{vrgydmJ6U@aC5>q5tCYt9?^hg^8fw1HZ<@f+!94$i9dalB+W|`$ubEwH zwin0~#A(>W^%25;JLw;4S!cTv-J+WeYX66fgqAJDp|xyQ0+k0+W=n{D!(tY(R$!cS zk+u~6BUuhzkCN%QA(=s5m3t251=R9N_$Be7&1ekXeb}3WlwXhl7I%VS=}#`HOLr>81$lg^L(Q_gti=u^Zo4yq zC_{(xenm*kQSWnJmcAu;PQCOyb24+R$@M_FAyuh;tw&N5;>v(GADTjr3S9s@u;9M< z8>~l^o<{372CD4La)D9dqC5C{@$D#j{-?FW#Nr5L`;@NuNZ`x!J4QUK&~K%~-{&-| zU?@$(4?1_%=E4TgVFZz&T|0pw`M+0kw+tNMAR8qSyfl}#po3c6xS34Dr})q|qTopO zN(!BxRBe(>G`bYO7NA-!eJGc9=BN3iLpN3}r|H0IiRJT*#Kn|1q}?U!iEfm#H*|oQ zJI*!uh;YZ3;c}?Ne3x}Ux#vl4gSR514ZCjr$%W?{2`|2_oQuf1^3@>L!@u#!!Dn15NZ-&F@wXeZqS9{mo$~ z9V=gqFKRBK9g-q#6&A|$py8cmn3zYDYW4WT@=Fs0q@$bMHhYYO}c5=!BK7~ho=f0n_-F=(TRuf>EZq`;rxN)z&P>mdn&PBYYEvyp~b+cOndZ)?3g@0mzMajrJGTSozd2a zqLfSkne15kUCvJh<~T|L470x+6CRG(mtq5ad6)f7dAEuL^<^K{+cD(}>BV(rsCu}1 zkwKj(P88!#1SIcWs_&oBEzs7Z@;K{2N8EMr3WpTu#RrJzZOE|4Ah~ElpA{gnIAV$= zdJ?A&&;7*V_;48bdPIDb3^+#|w)L=M#7BxkEY5$L`%<^2GZow1YYmAXs|&?#ACIV| zOww0y#v%^Xb6E$$OlB&9hYMY`{DE@1llK6UL^-^37n*x`mZMm>WMMr4IQHUV!gLvo zwkA=cwy4G#K}B|Hi#PM6YnNKy)|xRaj6Bch z;jykJ7$TtdlIt)+n@xn_oDG#!6#oU!gDr78%Q+w!KTaVXtIyh>3;LEe;pAMkm4My+QBr#*69})d~UBv+#cF4RXY2A+PoQD;< zs#iCZRkKoGGRl`gYxM82qY5r%M+MQ?MR{VD4K2odx~{p+I(-v$eQrrzcn(hbgmPEF_&GRW2_IBIFQ^}UNh^A-0&6a{z zAnkAu8rpTzzV>kaONzH86(nj-s|C5AGCw#`;tgUz^{_Mvue8C*hWjlJB^3^(WqUdQ z5m6WKw`0MqZnL_**kBcTo7iOvr`T%M<==ZSubDK^`51y6fwk8fX`ng!x&9K&FuK6F z;MXE*9WO7?Nbk3+EF8gOl3H$888F{B>2Jxo9VMM{zX&PMz}^OW!FW5A7o((HKxAd{ zX1^Ctczt)bs$#0R9TVD1U3`8j@UzgRsLm%ur>m565^(joXT|##Sh8cI1{sLHWr~E^| z>()QD;CPlhWb~#!HOqSuU7I^9kN!-o)Tty>@AZptARII6kes8_oG~@cnM>!hqYb7p zFXvHGHO*o3Aw^@vgnC7z4XaNs2NjLHhg6fQsQ!L+{pKYJO)LL}z_m7{d^Z6TLQ4$l zQJ{ReTAPW=47Ffmrp-jvb_JOYEUAoZ0a--R>zPU*newVGc!%6R%u3P4xx7i&xjAgd zy3D?PoOMZ>u{Li2Bo3=4^)Uom` z-et#Zybwgs`KxD**%JK#G~L6cM`W}3CX4+i6C`)r=}A450Aq679x6*jSBQgZ3?&C= z-tswio_J~TxA|@nR0C$|-9C6->f*W{IhQntw+DR8Dol2S35&LLVOqlQ6THyEQ_(ps z>cs5@YIMpHaC3qI-7|wMNd0|kXS4_v4^&r)!>x;)?Y-VRN|Sc(&7~Jjq4gCq)8+i+ z?@ZJB$c{2P4mr~FLr(B`z#>&%aXa%^T+%MHxvj@H0Cn*=z;t zJ>iu0`4gOD{VW$opvi(}oM+5b$>-vRa>4M-%4{3s2^CF+wq3xOzINlJu3TWDvfzT&zZYdP^TWS$)n5pN9MkxrQrbU4Bo0dL{6SOPdd zD9TUgWl%i)B~*4-XdYj!9mL%nq>F%0qEfB*)tvF(}*JR_B$M8vH;_I~%GaG~RWVSGV6i zkqu~aaNvSu)VDdnOYLs5M_BeHi-`WShD>Ubr%u>#B!SWtF9A3#=JnZbMHEu%3LhgG($_hZB2F(=a?W{Kl4&legIFkb#c;7|4`K$4SEO{)XVt zKT#B9rZ12w&%(Xrg16ZUBWSa&kXcS=0enNhc=KhZjdklZbZd298cI|US(<=Tm({r2 zUh?3=Jd}TU7tNlxA{Wkw-~jlf7X{0};i>q7Cm1Jv8NlR5#7iQmLpq_A5a>~PAz}<+ zN$vg|mnz{+&r18I-Z=-mlmb8JCWNV`!;C4)vE)>bXCz(1nVV5ECM~3915S}Rmr}L$ z>3Uh*A0R0XL2!1)v$V{S(76~~TKQbJoCGoOe{pPvlw z?VSl8bz28VHJB3uxg-z7;tixhP5Tr&)3^CCt5#I^ty*4+&Z&7qyKHG#skG_(6=N?i zg39}vAE&haoc6>Y(-1z*Pg@M)%oc|}mg+2cwx76IlF5Hc&Jh1-l{e~=C#R@nDKyj`^O^#g@2g= zSRO%1JLmvcJ_)~WCp?`BDSi0nDu4WMvv3xuv8LhP=C~y^N!(kRMpw&d=pmMb~t@fRAhd;;LUPm06~qEea6(?i6n|5+iiw~M#!ytEI~eO zF8|bo+o>ZJzXIGy$qj>qQdODTu5Wd6AEa+5PzUZ7B4K6eKpOF_?ex^j0fy`3dsy?XCEaC)Yo3@;2!gLN-oO{5#nb*iYV&^* zmssnUQIDZ<;IIlv3q{CM{mB8YuMb?R!~+qo_F=e=cK|03c78OhVeb+RqSjEhh#;ZT_3p|&CSv=4uVwKm_`$YI}|Gh(lIzTLh58Lox>-8BXb zrTaizGWQHuR+{O?J= z9N9({bxw<|KY4*0xQvI@*mCpa-+%oH`l}T_?#EdlRL7w$T!6Yr-9xwC-u?@LBpZhYr;#FHP2~ zDy_n4OGJR~aCsJ&fovXBcUG$exT?!&?0A$A+I-I}Z|uCwRC7yNOUf7=x!H(6Pmcvp zi>DUQn3xde`@G9T=#j_X_-^$5=h!P=QIpFL{<_zlaq{jCNJ~G* za*+}UC|p>?pNl#mA5=OVOhcs1ye0c+3!_RIH7rCey0{rYd10}}1jC?uPb8#2-EZM` zgiMh1#=Wp09JS{0rf5AL+gDcdL+hg=Fly%0&L?9jqo%1#K>Inty68eU&En*YSgB(< zKS`JaXkYI2dwlBQtSygH<^E;#At?2^9GI>jRs_{CBGhtR{2agvyefsj1JfvAET(ol zOr5GC;4+8M!rP6gmz7O7r?qNCAP$u-^*vn=dj)PCblIQ){dteY5?AHmq26T=>ML3lSX)ntQ`B9@;@P|=i|>jFy={Pwk?5werCZccJiyrasp{o=0>(Z zui`{K8h{M=hfBdQJ1=?4y}`1a>l55M`qRy+KH-j??g1iw@S1n{9_`5s$5Fqkb67*T zQa?ok zn!hxDQ}@W^J88DJG?$~o9#Z{kgunNZw2P$ZjXV^c)9|i!Lz2teO-PMPR%7N%T?QlR z8HaVezF(h5ov7thR?at;)}3^po*L}_#T?igEl>F~IF1={bYH|>K2IS#gJ};ErXWA9 zJr$fd8l^l%c5txoGmv@|a&#YTv0~EOx?b8o!hSAzSp12yp6L#FOt03N>(EgUc>qWg z_qm&JvKK?5G36+)u$4KAbCkLvUkf~LKIwLLSx*#5T>u|>{w+KyH;^+(&EdKhBf(sV z7e(7cL=^5&=u}jYd?rXDmvZ)%MbE55EX|7tGE$Yu9K#yKZDKM(v+_RC92p zr7))~qoARjR*Qvw1wNV}Js6Q1rSyO= z_CB<-p`HI0+5S;@lf)*tMoe?XMDW=_wEqD8!c32<(%`p!9uyS$esa}Jb*dofb&Iq_ zDWl{Yd0rt7+>kyX)QU7R#dqKBT>YC_bG7Lu)Z2HZfsnD{q-n#}t2S;gzz!Dd`$Id3D0ar&6U!rJIB-GwfS1^0>-7-IBc_D4@h6>_g#&Auqtx?6oG-Om z>5?U|f85)b6~y?e(!V3nY}P26*o2TvAUHK4&b4a=N+>muS_a`G)Z{3(UVcmWTYLP6 zO>-aSdQ6{?xjUHr(>HjTI*>vH0*P7#;&3#H0IQbhlQI2DF9 zGjlV&CxIv0Wy(`{;KnFPGv>5@tYlj=MF!nnrW{dWN2#3)y@m@~U-5UHoLJvjkLC(?z^3lDKrRKA+$Dp1(0iq`+I|MI z{IJFinEDx5|zpSq7J8+TsqFW`j18K8{}-c}wu!B92Fb zeU>4-RrqAR$wH9iC|=e^$?b!bg!pt&P0^JRb>@B8^`Sl$ zK}_+u6!djMcql}PmpuD{`NB{#s{;||pM^~LM3KcVehTBGXk-g?l+Bhx?-4#eMzKPZ20_PH zi|OaE(TM~QCqwM$2TkP)qwsGV_Y`_9Dd+xgT)~$7O|!J|NkYAeM9_V0b2;b{r!S8{ zl~l?uQWCa+pv;$WMUr-cUkCu6aiCiXIK$>t1f{5XVdYDgP~SW-!x;|wiKOWJKMGso zpYGNC6mtF@ zV^iVJSnD6Pq9<%T!N;1j5$SZL3Cv_8ptO_g-bsdRAx>5aG7b=OpTl)R!iqjhkf-R8 zEmPDDB2@%qOH?p&qJb;?x3W*;y})uUW}IIP&Eg%LMXM1ueg?3C`D|YJQ5<@LzAA85 zZh!LnV@;66OvGvCy07<5tez6$3m)25BMwJMzh4GJjx=MlQ4BA1uf|4s+Wo_T`*8tO zz#D+3;iIbHFWZsaLRMX|D<->!qDl8m{EMQ3GmSXlw`YvY3LcK1iZ12Q@q^`ytteY^ zU%sl-NX*rO?{GH0By73s2jO9yeqD!m8wXa>-Zr<|+X+*+rkM7mnXAA>l8q3jSx?}$ zPn6H9>!QPxim&wh#ti$^ z(tmBRc%du3e=wYm7ja%u3gq!YMix3JH(iI-STA%L(OZPk5sST^7gn3mpse+MFciY< zucg{3cu#_`-)#ClhQmE8u{QY9yj?Gx*aJJ^-VnXWSmc#Qd<*mRPpk|qWELWoQfdI$ z`;Pq=B~U*l?oMLJ#1E!%X2vP=WybX+=266XO*owZ@Jcu@TBiI)goYS=Zp1-or7U6_hV{JA{{!$H zT?iXx40<}*8MJ9G4J}+=NqK_C4V>b{P{~0NTUg+*n56Dlk^gSd8EUoBz?<858vFLZ zY~lF;B|HC7uF2E1|Mx3IZ<6o_carD#1V^kF&==Wfd#XZ@NiMLUPmJJ|^(Y$&%*BK_ zVI%^A^%bwLT1Xx$80WGcAg2dl6)MGj7n236h*U_patA|U74tGZQa++9m{cLVyR|P!t}!@Am}D{wCHV*%r>OSMcjjnxDUlzX#;} z`Yl>$Q=Jdt7QRGEV&wn&F&AFS7;^$n9Nu-cNevEs4aTEB1>6}7>D7vt( zu7YDNu!QA`rTVDcK21&iWJ+Rk_M9}-Bj?Mh5&~5W0wwhJE{6Xm0#ig^)-at`-@Lqn7=hR64WciR2CO~f6uH|@`7gDamXVtf zHhzk_UrUQT_-th`NECI~R93#Sg06}?OI}>jEEMT=yf7bb@K|A3+w=rP_aD9j+Y(Ny zXF1*zja$(k5?2%qc~3=HVu|Ea8w6kGvn{;tlh$WXy<l*01iG#_U*iR~1VV#*uBiRLkYHymGn7lEt!ile)HOEP$g^VhrAqTae}Z}Mnq z^6V*0PdMqU4SyxWpRAjlnx?-eZ!yLjyzvgq3f>lNvqb9V2A5Npek9P+ z-FY&aNM|SLCfj>_B$)^+c8x}{m&k;j2Sa~e5-1W#u@7E47Cscr=O1>XOsatw8}`{0 zTd#Pn+NS^c&g7c9$+Q%=oDx>1aO9M+F z5Y6-t&yO{6v-0M<$32I9y}Hdp*)Bpu_<;kB5~(%{emOQDYlzL8sKT*z$hYF~zQEu^ zxj5dM%oInQl;_Z5=@;U+lk{% z8R3=Ag%*CGtnEjF?$#!!)gIl_Xa!db5}KUU#0B}ykHB>{75vc4f5{qMddgj^SzeaZ zH8;(E{*xx-_AHS011PuI6&BM|MLkd#Xmtz3^IrI=lgnI%PC-|TBu&{)nBC1apo%s=;%4|u7f#&Z95-v9%;XO?qH4CuTA z*t+C+Gj)Q5m^%8~(fj?HUlYr~ZTHN_@Y(U%1;0JYjTPduUImFu!NT;3a|YQodZ!ql zcV&FAPkOD} z5jVWPL$=qp5>2Y@Tw-b26PPn``G+E(NhU4(icQF-w{Q1Nd}ss=l^MBr-A)=yWZR|{ zInAo2Yyip1+7xsNLne0L!ZMtC@dAAiZ;fBwZX#+?$K7CV1*!?-XFyD!%HjwA?m=NZ zx4NyKB$X>2ck#|oBDGmRXxy&E4veDXRy($Vw^e`3d8NINJQJgr+WpJDSdy+ifR75gC>^xUy$yAKMVg^^jMVJcyCTX)F8s5 zids|wVA*d4fIs0aq- zeiO<~V3YI<&40r-E)yxvZ_e13{_hVPAR&nHVfizMBP|C{N>=e-B=`US^ejvEpCda@ zkl!^ekn&(^Aoh;pp!fn5NGc<|8Bmm16iCY9E`*c|$te#z{)17Mg-PXC%;A=NnTBXY zCm|JjItECeg+eyN$c0*wFwasxD+k=ce?2H%)5oqv&%ahIulHmQ07Ylxe14qsv-@Q= z<17->p|q5B4dUBjF^ z$KVqOPi7YIcQy}VRe7Zq_K%^Gp|%)`dD+;FQg|hT!u})%_A88HS*Km}yM^O1k$hP9 zP<*>@g6Yd=Yxh&b!D0IO+hMHhUZPUa9}ZfzkL_jP^o;tIg!hm{D8CjY0n>?5Q$Otd z?|-5B^lxBUNva>h9MMm?GY%c_t!09(5(Md}%kaEAlHJ zyv0Gbar|Vcx%IoABQA&k&GG2uu5NSZtnT@Crrnzxn zIDeW_Xw3jS)F=hOD4Qc^bTTp>196!@OM6JQ%+~oBbx&ogO0fw-Y_m53E&g^u2Wy z(9F=j5YZ5(qOsDtu5Exwt-IXM3a4}l`TM6Fg?)5_?`E9As=tdW$&)z#PHr^UGz`w< zbY7eVYc?YW-X&xlqlJu0qk{)UQzq=R(t~*H_pu+s068V>B;Kn7&jDoY&_g2Dd-PISHo(2%1T_oUMD%JSCxER(m#p8PD3JqE<%Etkk|5~QfI71tD^ z9Ly#;xW=*F3q%GahmFEHKVQa;Y`k$dH#+5Q4shW(AG=RnCWe6&TH*Dh7moI>?^V$z zoQ)f_{qPrgQlHQyWkciM%r>iR#2lX$Z5lJ-85g)T4m-ct?PC@G7)AV;wVq#oamduQn`T|vs zq!Qfo?pT7;Qb4(n**z+=@1l4jK-AjmjYfEf@GCXeqNYyhWFJcBrZM=Ypf;kiR+8rQ z$S|v(qu7t%8&+QLu=rRN(~Y)D_)H!7+N6+e8QX@T=a>$n$IOx9cr$)^rnqQ6Y3Jj(i*%|9bKL&Ys5?W>lx^w+vL9Q8-(N z?_XHdvciXOloE6`qDN1q{%yzicW^!C^ah1$Da^=}1xNgx9Bnd|HsDY*DW((iGY(@#S-l+QVrVTs=YM24R1x zLP{_T3R7L|EX9<;HE-X3zxs)8H74!zX=4?v!31{;DTm83vF#3g$ZIc47UgsjLQ+U? z>E1(*;5}>D8_?RMkk}y0DBd+Y0&qI3GX?Re$>S^?AJ?EXbCD zI1c01#MqkC1H#Vg?Gq8`)qRwHrb$*A@ZE>Gq7k36Id^WM7fj47Tf)Lh@7xLjp@(4q zseoA(lz;m^;r-t?1g>ZO-@f{PMn2BhV2yV=<8{eQZ}`&&LJdCKN>s9Uw28ZqJ;L^7s4v;$8A`Ao+X9^1`_Lvykw~tvV!rTIyLWZNDr{ zg0s4)-&s%z!9njSB1p0~=7Q!~EM>RsFa{-)pEuKeJ3VoVhOPyWInMGnC(2S8#ph;H z`E{y+=Ny?B92i&=mv`;B7M6`laMPBCd7ev?QwTtQAbUGcfv39@CIX3sj0t0E6ljqCJLnz!` zb5LwEibh%rSxat%rcA z`0)yxp?CqG_@p0T86@<3&Qt@`KedFZr>mc4AK)unLqJoLxWA{363A#TVh#EUPSyH4 zoyflG`%ZS5&l8jT znv$DscxRg&`$ihrl};Fri;}SG;F83*Ox#shN_s-qV!aGmeZLu9@Jd-u8!-k!FKf}3 zJYo``6WeeO|Cb=yt?P3*G{SUkO^c*|3n#Q3XfL|;j*$xvapgrR<|iBj&B4ieb$v_E zq!w0kEkn&Oi5eI4+adO#0W+Ah1r@?;(zi@0+99+*H6^*)(GgwD`+r9I8ve#L8#?fC zS{xIXx~u=xAY_rwzUl2C)+|fwZ_O=93Y-yzJ@5R$ADP{AaGZyvqxf{bT{p(;mK(&g z1{^nWgjExo+Mf$}!Ddpb$SD)C$TMJ(RgBM!W*D!5zVUiJe{-ZpG3bhha z;A{gPX8Kap->?n*Z-DKUfkq!BYJwrVPUsv~GouqF|B_OH;c2&|sC4*Y7DmrCH4_5k z)lD=jcM?qt6jjTon;fJ`qYD!O8O)l&fmYg=yC*9QlNC&kd%c{|Zk{-GXY zg8DyB*gl)?yI#YH|M;Uu;R$}=-i$?e#T#23*=XbjPN^fLb76@`5jR7~ihw#8Bxx?= zI4E(MwuStnvT^dAhRf=>&!-;AdB1;kN2wlQQO2Rm>A_eT%qKfiM3P(6?$21r1Z7U* zQ@o;|nlk`?Hltehx}Odug8zp4%9gyWVJ>w|4qT-eQ_N$RUk3M-`6Ek|@T!s3(}yBt zJ)_&;+Z+uKXr4Q(`&%h_>iuVIR^zfqS1f5{QjYe9e^G1-0HnYYj8g$*ka4pirG4a@ z*P}z}j*`tGi#3ivorW5|dALrON8^Pt!z#4`iPPHRh1+=B!FRW7O*gDa_dN&WP!3*9 zi)o@d2$A)Fk$h3l;a{3a#Pmk%eI?IwD+IloB7hJ+k>9V zaW^%%NSraD!8Eni#GPbi^i8|_4V+rGM&yY(F@HvIv4?ZLhc$>`{U>l0&>HXC_CA#F{|7~*^%UV!RCIT= z68idl(QQo*G_~A{RfoE3Y>UIoar{S303vAL?rqAo*0%aW*!g4MScoM@-T-fA3T(>! zcWMX@Ax^v=Os8Gyp4y>2B9VYcLY*g9*O--C)pRsx9(0any3x%OR=wsn!_1Y7{q?K# zWw7YqNrRoe&OhVIbPNGnv)f1M*amZNmT1W94sVnBR-Q!Jugiq6VK99i3q9`3_Q>_A zNjPt=3doJ46zsNOM*U`72@?4Y>+>h${YW_WFl|@#>#AHaUs4+Na$Q&0@eY%Y?kNgR z)O1=6UwnPE->SJKJYe9B*g8~<_WhgJ2`>r{C%6U2rYp-D7;%ArB!m}nGQq8-*A9BA zEUnKgf`1oko{3%rml>o!gy(3D+bvUtHaQh++a{nkAKE$r{FBgt@a4C08R>jW(&^LIQ;}Q!n ziu>wfoI2@~T5h9rqL@Rl+9rjy4}H1VN{yGO*f~=e^obb^8 z=}b7~$0-6sYIZXu_%rI4+PL8)Dm(a`cWzvasSK-tS2~#v<5f26YC$JAB-*Uunas0y1tae)wvK`5Jk~& z4@W@jbA<1r4_^i7vic{Bl!(fao{$|q(D4J3|7xS%R(=G_&@2hjIOzMs#{WD_d14Rg zt`_+_J6&=rk}PgYX}9SeS&}oGFJ5IlEI@dA!mtg$7OZ**KYp# zc+z`s?pHVvXe}yji#(x+U0B-!5#M?Z9@N1Nu46~cQ60MD=sfmvvZlboVW2H9wCNL+ z((RnOPH6V;wQ@`onqJR{@z^^94yCyJ9E2XQ6!WnFlWS#pz!bq2ckHxZ$(4^>KW*n_ zx1%0+rY~8jUH7XB)6f!$y_5cKS~S1Ff?Sx*_!m7rYBXOC4AWFu3u_xu*+Y!s>P-0L z!u+{y-?9N2XRNnk5^}$7FTxHG0{GfHL)LwmC%U-R)=ei;I=e9zp~*vFhFR}Gp=tx2H4&AVtsffJ>7~*mC33v(>F~|u z^g%78p@`A$SC3$MJl0DADhKB&z-71Rko^)`XY5547A-3+xNHmzRaTrZDQ|}zn`^eW z*|JxQC-^BvF)3<=_AkotayMp7(kYTuNF0Qa&To_8sF8?kSYZ=Knntd$vwhqHlU><)d&EkxA-Su?@ z>iJmWe%W;HvW2mScfO$L8$pU1Yf(!fglgRaTE{X`nu!Qx&&M1%0gY$FI70sC8EFNG zCK0(d(0_`S!ML@h7d)+9?LUTvI%M{b`{QYa+tly&6Y+X0$91k#=^dJg^VTxn1k{i! zmv6IXRMHR5BmN$rsuNNc!N8xqyWzdZT&}cmoq)MS_erARJs~4so(`fXsPj9mZ{mzN z{)Z%L>kzzU0V0@2G=}D|5%?BaW>2G_uNnCEn%LePkoUv;aAQp3XC*kQD8+RYRKwvY zz(=Bbumtqa3@gv2-ThvCJD_OYu#>D(==g-zJ(21{^~tP4(qxt|q~pU|JKYpa=`HFZ$_4jJ! zl~4xrg%}yPfv_45&Rp8e8Ac_v4r44{Q&p~9)pjTBy##%94s7I9w(^NOLDK>4VX%jM zQExUotOP?DDc~Hk_4rJrZIbyv;61pyK$T(@>~{6-^kvZ|?)PE*jUsn1uV)KiZ}FQ% zrVyAC5%a{F$SWfcT0dY*ZIQ+X{fS#`=0K0vPb6AaAX!!`eiotwhjoSQbBf_VozySt z1V+zimU}+wKOgyja(vvR=U)S~ouyfBb|M%zO;Wf|-vEI`hHM()w1x5tIwO;}^u9?E zI}g%3R5ehpx7KM|lRNHQ*bU+A{nC1wZ&}=gGVVL#<3yW(TfhCFt}+gLl+uav3AQ@T z|HQt`-{X+vYlIf1>)0I~TOAIA`u%aFuJXlcmDZH~_wXJ}y{4sqQvZ*P! za2~FHv*S-*A5PfOsr)%MA^7z7e!^29St>{VeDN__TrDciTKxub(o=b_2ZdQr&R$?d-a_Z`NBX};jvX^i_EuSZ*sHpOMpQ+@VnmEaM=3vs^(l^7;{C@PpAtF5 z(0Jg&svI}ZIO3F-NTl*gl_E(`t)qeq@HES`Xi5j`uxbq4L zbEZF{tVO0Dd7S7C8FSKqw_kb$!86w7VXwFu`G1J(FuE+^+-1e6TC3i9F&sW1$p7m; zjC%MXI3ukS3_MzD*O~AfiSk6Y*I{_K+tZpQhdfJ?c6jOR7Ds>mct7Jz?S+_6Yput= zs6`4*ddeHx3NO<2V~C3$i+8AELG2>vH_47VXpCB{*D(_4_Jto?p2E5)<3~!_V72qa zmea@FxbJOuQ?*(6)*x&8S>-B;WoH4?RG0IO-}T2W%bg?8j5`SS#BbG=)lgL48Va^% zEU;N*6%P%{Ue{bEGc#t&_P56FIgpq2-D3m>B`9rVt^aYDD zDbv#45kEc2iR1Qmmsq(9%NE2*-F%pZStdwCK^9;!(KUtvF7eZJ>(MzM&b@BR_^ z2*fCg+0LQq_9C0mSSKyKzj`(|<;~$go^Lk;Z9VL8RYBI}lZ;L54&gJZbY>fB{gL%? zuS2lh-A6M*3mE6(D)+yGS#r)L7{fx3t+<#|goB$NF3u<3ame85b|u`YwGCISG!SH_ z*ADTF6)FG4GpZ3z#&E-{_wIrNt1|*!8J1l9K@D@A=?XPVOgu!>Gs;tOKk|Ft-V2Y| z{}Y{~iQU-qT2zJ@D+cZpD4K;gSU;(daY8qw>4?t5zJ*3MkL9uGfd9Q%x4uzQ=Q)+S zzqk!M#(T~1WHb~R>+ZM9 z-tO2|&4=Zn=^RgU(mff{%G*6BsRPTxnytDSndZts{Jr1Mf4(K5SE^ zs{FmfQuk1m=?CA}nf!r0lcP{mZ^j*+TbE}aVk&JQj%?`B~RVo-J ztk`yP8y*KlM!VeMfwdpZ3)BczE9!fB$2qxbAu24x&Z5H0BHbO{7%R2x5psKg%{~vv zj(LooiWBC{r^U7Mbo$XwD6-)Ri~&HIf#&V^O@9aISD$KwH4#|1Qd$K?cLLKpNM_%k z-|cj2``JGmRx_Llo}ALj3QlSR%);`uNIl#j>~!e*t)jsv8Ob}-SjPmyj?W{YN>jUm z+XbPNJJcZSiB;wYTVTcdD&&>UEXzI7ari-(+|Ki8CLBILzB5Ozh_A9FWm_<8+GS5v zxVrq#+}m2jeSUNc0j7CUG4DwmS29b_?0T znI!xi%@vjse57c5-Ug#>S>B4i(9SdDJx<>M@4Z15S}ztKG3xQ1;sK+MZcJ0+s&q}# z$q05KJvwcAvE38=6j9iI)iB~B{^j8JhTR=goi&X3fmPKLMNOMuVFV zCwYzt+Ada{1I7;xpYg3^f0HcX%Fl1MM*7-NarY7tu^hrV<_6P;QaU{vq4y08M%!~i z7xe}<+%9-4(N9tD)-+~NV@Bqqo{RcGk*G1rkz8T3uQ^QiTaG$Bno-So%FMslVGE1L z$OgA?E}e4NWFHjEh?RWVMRRaub`IJRJS9f951|L%f4rP^qNY(0jV+x@nK0@Fycpw< zixEnw&*t}k{OonG!pD8{B}-i$gYhyP$YRkc2af*rOw72%k6Ue*$>I0K4n-W?c@g6o zBs7&w)m01ard0lB#fttRr?}eOgG7z`%OJ4h7fV6%e$N_t|6h`Qn~_e(T*|ls<944? zNK3LyhjTtSFz89#^9W>6O(V5kW|e78JaeEM)b-i<=&02QFPZ^+pgFk0&k%h_ca0l# zX>%AJG2b&;viui)m;9veRlkHX8bw5{=ncYUNgp;CXvd`}B^*hWPG2WciafZ*%?+p? zM3|4v1#%l+z0SOX5!}AZ_<(mo*qGFKJz~x{jBMj2;YzZ4!oBDW)lg1lwTPa(Vx}Qq zn!OPxRn>@xPJ<%OyoVhFCWy!r)%Juo5(27OL6c*p60;nk=^IF!M=YWs;#hZ)b*oo{ z!yLYHAmdnJ2U>=~`4ODa;Z6RdDWz8Z^CtGkaxYxNgILCxjm@eP)DqUORlWh( z+WgaIA|>Bq`sMgqPO7(WT<)lGnCTIB6Ra{UXe$)5XA)gEa+S^d2Rw$(9sWkD;PX8AsQQjn=?1 zYd6E3BaQhyYC%29V}r`g?zd<~CAks~H0A>605fu{H>VOJ?#MgS4oSRy_%XNGU#xIc zMGHL6A3{02zIaD0$_>mu+@)Re{j4kBVmMmEbnRb6(#mJU$aA=~;&MVX=+GY4TD2FC zEZ`qSBS1c-U%i8GG+KTUE{#njes5D>MK7`8ibAB8C-?u!If}Fx+Cu(3D`YCf|45$` zE?I}Ck(Lk{l>`{q0!~D{OE~i_-Pa4xWyEsoco1S$5H#>gwru(CGLGq^zs9F#tn6WN zm5&X5G&5?f_^K1GG1!~my)QXcRfZMx zb)*~NUpKpDPI`;mx3q4yIwV3`28>C{!-oq8MG}8H&D?N|(4u#=qo4{mKin%$>|}AT z!F_9A+^S0*^o@HHJ*w8JsKFd~@GLAyIfH3R{?l%O*y>COFfSf?(5OnUZpV)jb&y4# zR%I!0&W^<1xt~zq`>jljAgzfGMVe~FSo;($hn{gT&vJi(!sWdERHk^Pt-%g52{rho^_4&w#3$6aue=165w6Fv@f`AKs z8L8r!<1`^*qT~~n|8gbiZDh?1T8`f%n0E5bpzyiNqMfd_Xjfo+r~U{oCbBPrgR#~y zJr~04@*U#SlMmXM9}me`@$he5tZ9&FruuBHQF;#<`IBF~ejO#?I=PUkVKb=f(vr-n#l>d!fHcp6j!Vg+*(=#p_9-?0D@4E>v9g2CccW|;Z z-_Csvo;V8A*s&0UhUzn)UxgOD{1QN$Br+~xNN)3em;Db>N?#+`)ZnkDF%@i`@}`wF z3Uv=Iq2~t%1HDsNrj`cas^5{Y12UC^WLHO+^c)i>uZTZiR`jqY|AvkcCNFCcj~NxQ zYr6g86v8RkhM9R6iFm6gLWP58O5Q)_YL*%xn)5a#kRP@ADzIlaqb17Y4d`e6;=DiM z`1xR*B1;VKycBvVu}8iqP0)T86I8Z04o+5dO)===G??%uk=-2ev$U%sjZyZ+pAWO=EdiKQgxy46;(b>Y4(;y8vj)zi8DfH>7h2mDAI`-rB5$VyCahVkb+2~mZysS9 zM+kd13%aCRE5~A9Wa-?k9V%|@Ks!W-<5l`a5-Gt1joE2eA9YDKLX^%#PLERJ^dA^# zXIKFS*-Ug=Y9Z@qY~%Tt^l7>-S`S5{nVp9$t%e$O6FMDQ!QGZcG9-==0xpC}PK^6n z73J|&Je%R1q~o?^C`28R1hD8#ZuMN^u^Ex?sVj<20>orrwU@6L#f>E?*OiwV03@`y z2Jy@@lL^X-v3nzA4me}ofu=9b-dAUFUC8dHH8fSv!IMv zEKdDqR9YR~-RDf=DN*X?_jn<=i3FOqJ*24gFu>v*1V74;dcW`tyAz>md{fwLJ#hzj zoyZ_#bA@J3B;qL2pNMXq`-v_)tTUIokw1rGQ^sz+g=t2Jw2ZhW@(*$w1wLZ*{oWwz>~h4-On^TBa;Ya=3vf64 zEcxO^>tCHrQggoL4#Qu6NVzn^dK=t?vE=Y~Y&@T<-m&Uire$3)L{5(&50+| zUz%&mSqkcX&TF$MZn_ht_7Vbi_o;kb-=z~KOYtvL=%W;_`-=mH-LX&(Qlebhp+B~9 zTLC3Xc2B58$(7l7Z@g+`fF`)`!|(z3dbYQ2H2`fH6xZG=e1d!PTR6KoUO0fO1yjPU zdelYa2QIB}<-<5{O7xcU`|f<-fsfXoLCIqH)r_S0y;yugw4$BE;LDQ*o7{yR#Qx^fmKKqt^dBKnK$-6gNOW&JR@(Ssj-1jj9m`F zq4<VqR|wBRjD19Dr-tD$A4 z?Y1#Hr4SV-Jv<{lq3cCf#w+*PDvL=iX2yA;MZDl=wl&~ZdRWUxuY;<+wfnz~?``qjva1{`DJW|!?e3Ty)> za9vc>&`beOzTy5$6p<`eu^t2_r2qCBA*k*7hL$dV?39B1(hKXaCx*{HL5y!_;a4~B zRaaR(FDP|0lmJnzgk~%y-x6rrcQ~6Ze|!!*{^2Hxc!y7rh|lMf|BiT&L{);+Cx&l( zWSrX4v_yNOD-7>HUD7Da2v6MFfnQgCf=e!EZ9(+VcuH#&`|Fe8<2OHoEZucEefS_t z`C^%P-r;-eJl~i(gBw`_t<;6~#lAfaXe&Fl% zyg>WEzTmt)H)C7Qz)VQsrmhnxMLM;{Z8$vK(Eo$7?u?cKJ&Di^>5WEc_(L=5z0;k( z{pj^q3Erxm;2?FygQ-HhQ*`)+LYeq+jY;G|ZHq%ES2Nb6yumCo3yDoXdJnFYNQ|?{ zoNg!BGq!Od_Wq{z%ett=kf=LyL#8ENS-^BL`jXR{uLQXxz^=P5SWUTxRd9`A{cqAF z6sZD&LxG_VS1TS0_z%n@_pa&J_ol|VeO>-&U0bQ~SQ9V5H#%fXjh#X8Iz4_Q@+7>2 zL!DzDcZaFuDyktWalgxJyTW2f305DML$`3eqV!9P-F3!#ZbfA*5GwuwDxFC09ypyY zxo0RO20rgbd0-_X_Qwaza#x=&3vAMwG(NBR(_&t=3`RQ)0$$l@j9myW9wH~C>xwjI>>Wa_>m5U7>>Fd;?i;?yZQ8vtoow^!0f39NN#+Ro zrw($|k=Kyo{4z@DLxKUt!SbHex)Bsob3n2s*w~Y?*dh;D-0m%&1~056hsxI<&{2qup&Jzh8p1Sinv8P=KWyu&w+z)-xhTb?1eYr}ZpqVn(>ZHZ73M=Me( zROGu7p1dS8KfoQ9weIb)c?}nT#XB+??yiu^BxJ+5)h1H;IX7Za%(bfRgiqq8|94Y?B%|f9=Ta8Q*cG)TNm)EWP-~HL5#MDX?lieU&qYg0R)-DqZ(R0 z-f;aag9x*AKmcn_X8DS?sM8TH@kG3L(_`-8Scj4dpS*k5vic;3rydTyn(dDFWTz+L zDo(>L2d9Yit=dETpllf~ryVx^s(Wt+~eD*Uvy_7 z2bg3cohvM9@|B=(xC}0Cae6;w?s*+hFEKxBba#fWt|u5r1xnjxn!TBQQORZWV$v4r8mPYIaw{fwWMXbK@b&CI2y$v$N}!p zSxP|T-}H*H0c>mpu=}L|k89ZOhI?N!Re#MRTh8v!Z*DTz;TvVK{%yF2@fldKiW#lQ zHvrvTb21Wv?f_WE&##Lj-G+O{brkq!>x1h;ZfheY-x6EvKiQ$aGUWn9e{eUJ{0Tx5q97 zUG)`tg^4W?5rK5+8&045u4ivU9TMVSeYj}i!FfL7kO`EY#oiE{F&=ix@qP!$O}DCtNn45HDc4XMb=El= zr2&gGE=h48-1<-dtazx_Yv?p1g|7v40KGv(R_U+(QgMouz1EA9=+bw3`>rP}9uJCo z=f14CYFb6gzbtN-7dfmS11@V+h40LhD=9L4rYC2gx z{%KePd*jRpT#Flqt6E&kk}j&8p=K1v<9op|#dvbZD#It}^ZN7S45zz0Gu}h@ee`CY zAC21Llb+MJhq!E6F0{-FKM5>}@OdhzEyRZPp-VAL0w;wGiSdE8pUZ$8eSR2}sD>Pv zf#$xiX{PKuLLW|0KDBRRlIPaoweEol?r@Ec!C(1Sn>3C$pioryH2(gD=od*8WTE;7 z>e1Kgp?LyS2RF+vgrK;9Tt(gq`%NLw6rb$;&G?b0UYDWuO`i5-G{kL2{3FU>j%{Xb zhud2=m6}|bs}K51@+5Zx&05jWjG08P%c6y9H=)HK!uvm7<*V^EsjjUlu6BgHihX5h zyi{g8%X}}7vIky%cYu#a`G&mDJ8IsVX|bRK;4@5V;o&LQIbyvs%5@E~5}rZIv=m{F zQg~xW9`3L94XGZ6ED@UI*$cFm6Qt8BH^aCzEy-V`UrQWHR5t}>CuG|F=WmNZTV$zG zy+|Pf9KrkFB%na01*d;ZF~z+qmTrj1QVBOE9LP}ASN4W)6-IKoownMm&w+?G#>-vV< zLV*H>mf~96-BPTDU~MVxRtQkEKyV9Ga0?uq0x445Qrw;36o=v(oZ=2|&biOIoAZ3% z^Iq@#G1rxuJ?iBblu^BTFS5PqdDc%2CSd*p5KR63 zZ42McDDFpNJh-dQ{o=XmpsfJ$M`jjNja7W8snxfxqC)u%j&rtY+4d6d)~SqZ-Hr99 zk$1uM$Cdtm>&htr?Z_i=F*!Go96sCM=qh(~p`Ow|A9-J%v}a&6N}GOcGM+xpMhpRE zv@`~EQbFyc{H&*2cN$SkdEA+?OmuY>E?JK=9HI|k(Y?>$ z^a}R?FOq@RI8ZZ?{Jj7PyLgyty_9?*9R_@M8aGb>&YdMmA15{QJ_BxBf}fX7|D&0L zKBh9AO`NQb^`iAyqh>JdM6Z`DY<%Ub&pattCH(VhkiTZ&GF&=P^UD(d%lG(f-CwJO zJFy&I*xNsFTDHuMsAAk(O8(S-^hz@!?SH&bhD8yC>u9yego*#dapuHTb(HniB%Yz#HXpjQ zU!g>t;~`bl;DRRQf8b>^1aT`?5LhQX^dnbBo7LvAqS`j1#y4>_pRwdHHJ3yTJzDNVsvx7 z@zw|A9$h=kRU5zfb-!;u!gY6$FZ?X%gZj3)thd+AsAtM^Zwh2DUX%#85`X-PfjeJV zW2kABDc=S^zC8Ea)}hB1H+^1HCfkz{fLLD~@f@tyLr&+jI^O2B(Ka@zsECw@?2~XC z!IP|anI3*T-$@67)#)!a4BDeYrAFykvIK_o{PU5|GkGi+sO2)PCbeE3V+9r$E7lyj zU+I!#5UNp_fz{qU^LeaXe@<20QOhR7er<0*>9fCgmhKpBRUO!N_GD-%!NT~Kp{H7e zAo=Gk4Lg}{%jx~Fg)x=*p^tF`pcIc|!Q#%^3R?WiUPq6!qL#U=ieTzu->`L)M5u1~ zGU5D!{((85w<@bV|Imc|VKOvR#jkU=KW5<|iAU-A5YaI$)<_ztk(+Sf6txs)Hsa24 z(BWcV{Lnvj9YE1tHR{8E|JkYUiyd_fMdoZ%>r!%(Q{xN~cIx5=3&yEhsZJOc{my~A z2iRO#20P?Qs+D3yU6>dogL`wF{1f@{zD!Sdf=6;4rf;Z@)mF=#;{y)b`sQ!Cyt<+9 z&93rzD^q*}bXE%bJKY)XEe+d?p4rtiT}yeL8IDg zs_l&@!>PzVuyt>k)3jSb8hu4ic=5_^H##!X%j;}u7trNb8jzn`?h87Nm)t#7Ku~IrIlFNY^ z#BnSR!1rh5KrF*ma!4WPLqO9z2}3D5f=qj{(vjqkK(gH>7a!DZ(W-4}>qS=VocwhQ zAe_eT>4_Q;px2q1wMMGUqZVlHRQjp_5NlY7n0h0)a2Y_InF+4JC%#N1<-%5evAHi^ zZB$CC;i6PfZ7>EJL;rNITS(Eq6Tq;{H6$ z-_SnBxR!5^eUMe3?^?!%Ae*jv>dCjsHA&nPN%Kh|vP)=6+tAqKUkMm|y?4#uAVDf% zvpZwEQLnZ~IzhMmPYwkh)8H@9t^GRu89e+1sa6Y+J=aZJ^Ah>bO>d*s*MFUFuSj-v zO)im7>23KrmLoh2sJ%f!ttX5j4P1qDHCjG|V^Jhq-7!vN zPN-Q|`**OUyRGLoKIsTvdp>tY-|jK0H1_bdR+4?^FLDxS_|-DtJ3nQNe47wI75YpKC*IWX%=L>mF@TSaiK52>}1RSn?40lyiM= zd+u%Uwl0PGMvJlo*SH-|Cw%q`-(U}c_JKVh2XlLn!=6T+9TD0y(4%nChg!9_HGJL) zx+Zr~Yn;CMz3BuQ6J~j;b*SG&eu&B658NBe~afmpi_pqtPaZ?$g-IbGF$= zTIWLj(VRN=dZP|8NzL$jE$0sY$?9!mMkEN41|nu`a=dz3z9o@vYN!EBthgCzR(U@cYz=>SX$s}X{r7TR@E`<+rN!Qq6 zYNaCjqYC9zlH89L;y*MT>`S3=A$=URWGCgAiz!D1BE|ssnyG?kaO6QoRW^jH!hNi@ zrJD|SY=?NrtE$@?`=jy;LUPM=gRoz#R!d=NM9c}6_=#q1+Mp`oHQNE8nmJ+Kj_F%j zzaGu8lamTy*W{b70XCWOKX5P!h$l#!EESpeCLb6>f!>1ABAoa)80ORYGug1jmI=wb zmnS4o-B%^#tT?OkPOac?+{sWgObQ14qQ(4N6_EqcVi^|`CZciL&iy5s?Ihv5zD1w% zrTNZhNX;6M45mLN`iS3u-Z4-a?GkXqu`g2!+t9V)I7DW6dbY96JFab*o# zgA2f~DS`RWa(>Tg!K?WRlF40O(%qbX1esWx4&dvu9o2*^?-d%sTLLY zL%3t9lkrmf0=f1Fg?Y)AXL|24thzX8IeXfL;wbLr?0@ew<4^sjo56Lsyd$BO?RsYV zgYk~FYtz#0b<^9SQd6aZJ9140w5?TA?M-Pkz>K%#+4-B>LIh7D`c0T9UtB|vAJ>*s z4jMmiiECijm1M@nYt4WQynFVJKtt++xj__FQ&W%V?8!iQI2REc@Jlf7)b`J`{vw?|US zB?kUAzGbCuNI8i{cgin24}}R8P29ImiODwhRy7`T57aUGo?m1YF; zF`6*ugat@!<>49ZvX4cw0!ub3z)H^EMa`dlyo;cKL2###$T2SBS*vO{FY zG?-s7Ox5p{G+(6MRQd;X;k;f%-2?}GO3hHIyx=1pe%YX8sn`|^Z3=JaK$2SGDoNz| z;qk{ka}(qhITvnfL`Mu6C=W(e7zh32%N8>@ z+KDO>jZ~LB%;qj=FRI-F)VN}3D~EQ51|(JM`r#>LLiqBbx;)^0BEF3~JY%y6ZatWl z8jMd3_7d;W-d1*XZUyziXV+)0)$S5_A%Yo?#b3KnhZZjx_p8|*5~n8Z9)Pt4{om+D zxR!xRb*Tkk%O(K>bih?7m~I!7ZSs-V^gl_LZz{>n$Ufzk9unrNAkdyDV1iAcjXFbT<0 zH|;@hZ$|IYtJPE+dXvP3^un3!=>Kp_Yp>W;w{t5Ca&-qwe>|m3>nLlev;w9MyGK;hcl=3kQ2X1B@Y5?t{cnJ8WaCn{BSrTS}qNKILq;)fBj^ zxidY8{_fjy)~w;cDKfW2qDI<~!{t&Y zz&*8(L%LPPIAb{lj%=;i%6n&OIV8tXsD`ceQ-L5qRotGQ*U=(#c*-D{=;cp}vL~UA z@C&YwO-MUAi+^YYW9I=>=E+nhI6!n%*hJqN8f+E>2!oI?d|1rU4JG=6T?_(A6)e24Br!P6LqTwLscom~? zVdn(1>lIZGKf#O*GV?MABR)&L#{)32fDG5I(s!HGT#oLD+?#$f?QDz`eoh|3F;+({_)-X9O8 zR}OCVaaRFAOn9%bU#MP|2`!CL$tZUN$*lPvPzF7&if@oRA{C>wx*4 zqG$b)y`9~m9I-5{AYeOnpL@*l(8oM01Vh5glG2!7Y>CPvWQZ!*m~TprT*4OUbny*$Wo z^}V`kFzMYy#!Y9`I0v3ETC=B)jAU8Ux(!rHwiaR`<+@2UKs_1J zUuGt2z!urEKf*uNmA_BgZWoTJ+Er9mH6Uz`(nk1CS2k)$YzQ3OY zFy^V;h$m*^p5m&sCK(`oXJ#ABYbZriT&PmHwBxIR&qe&4qrG^b1%5@nok*SK(N_;A z%*NLFGBv5?NCVxSYu*=jew6X;CVr$-ATM9QRxZgjxmQRTc_E&CXqj=}8(bvjK2B+; zUyZ-Nj0DMT+sDSkWGyb_`>MWwJXff7tcYwMXdA0$d;5dG6VKRdE|GJf1Q`jFwwBpK zNKmK`1^wAbCHq(%(wwSkR9ITSe&5&Qn#R}T5F)t0N0wA(I!*j^oskVhsQlKm&|_z> z=sQIBU7p)ky%qUq#*?MKIE9Qp#?gcwhyn$FDy7;Q*g0u!I+JR{j|*{;Cl53d9+rG# z5TJlHyIq>Gz&=;6UQp)K0O_O#u+d$Q+)nDc{Hn|>%%_FI>RY!IPQhgic&{Vkmd_M3 zG;C}t9+Pbrz^81g11+<$+g#Q$nRbg?k5Ma|``h=^=AY1XYA8h0T45~n2y}f%9Mmg# z9IF>eIekEWcIMLu=foyzCoDR;h9DYe4y$K>QJ27;BVr1WU?wTe(Hig<3K!v)>R_Ri z*b;2tvpb?;rzS}eFAJU`zSiAm3C3%TA=_K7(G7CXCt&O_I1XOGUKhZRccW^r%#{h9 z#py5Ezt*J^88C2M?6J0^DH6$!J8nVzBLZ`S!jW?&5MmoHk}U>)P6VLS%$QkUYatSnGz?o zhpZZj9`L`SQVLO0=$I1sxL9E}o@)x*TJ=;sDYc>8>dj3MW|7rXM|Hhunxm43$<9zZfmG-;Q#cqv?Ridp@9!bhHn6T6V=!X$q+AdgeP$KL?TxEZA zLr|iYOU&{$OK2GOn3*_TXElsDn>luId(MJf^u;mT=lb3sw2RSo;G@y^5}sEwaDtM! zwWr-9mF!3SQBdR1i*4)tK@AHCOUoO#&dVv&3kGns+phAm(C%#OYKmr|3s_ZZKn#Zk zvh#@F5B0ID@#UdRq=#>ga_mclm;)=^kA1q<{=(7a4>J?V_AS!ON%vES#hGMw$8)?&}5Xnyk!mibBK}Uz8>DJ6sH(Cq(RP-tY zv$S6X12OF5Eg8DsMnb>Yc*j{}Q1(iw$9BJP`tVwVVtLT+QDYWA?zb+D!SZLHzA96~l=os}d|V^!pRh{q;o+iK2p* z5E@&4L|=UkO^kaPg-T0czeTkn=g&68;IE^B_i*Sc=n~)_PDI1AMSr84r2a_&thRYs zRL?fnqS#b?Xc_X+3`^ zi=68A`8bJnUlF!h#I&FB@()T_oi5C7RoMRn^>PYi?L$aROq7mxSYTkzQoS=CS(!#O z-*C28&UR|;dI-S=*LF``Fc$bGhL_sI4avf@x&w4dd_Hr*c}jDRQ{JsCQ(+qaB? zQ`ywLP%+C8F=%B#5vs;;4KV##NW+#FJK~kirV-fi%&Vp$TPe4dg3E`jpxN=v z+=g^x`O2nD;@&s|r~9YvPLg#7P`o7Z=mjgnI$EG<&Xe%CErIAdAKFiHUov>b-Y91a zM%Qy=c&`Pv)NMq+1TRkLxEOz)V9d!PJ*A>uUJQ7(!jp{a2fxjKi+#=B#XsMd12O{f z^Nb4xJAVVDb@;wDNE|u~^Yg2jCk$A9K`1Mi6LzAS!YeC&qhxpby2cw$-)!`(^6m0b z_(eAYIZj`OE z{;rdx=m4kFu08cLy`alu*QM&I2;oLrN6m)2dUKDL;KX?bBT>{#$2Z`9DN;nxzIoxa zVI=3o-tvv7=fu2FIE5Y4&twy>d4|Y&Z-?XolIQj31S&jQ^k(C_9)9e_e(leurWj-j zVGM@3H9@SPYl#{No(?X7>FZqB0r*#^C&g)1OEr8eiaEXk^$&1~I+eO7&C>np1%-oF zG3>gWKjpL_PO^}D*u4TkQ|5`o8O@IGqNB5#)PA;*RWKKl5o1iXP`?5@C|;gX388xR z1}i^WaV9p>$eBeLJAcPyW`gMB9DK4u$B=H`#~%r$&0O4mY|fPu__gIA+U>lRXt47_ zbJngH3S&*V$oDmgp6-1p`0hPWt;1EKnm4jU{J1){OktZU%~AVm9rfN=Nh+VkD+Z@f z-sRm{)(j<@0h-B3%!DCFt9^Ny<-0tIa6T&GD!S#H4X#4hm=dIp?@h@4E6D_q<*+hS z;6>M!&7bmM4wo7aa#%OsvA@;`Sn4-mScwb$dcQOb>P>GqBLt2F58gz-c&8 zkP9|e>8Q_Nb*d5LNHZ+G8hD~O35!<3Ban=cQdNh20O*{}F%BP6&?mPh57gou zE`9EMx0AEYKy-_Z$zHKz;8_d_RJKWS&t34Qk2HOGoT02V8~j|tomh`QVpD9WOL39F4M+@oUSn-g_BD8>FO(S3K4cq#46!A1bUEbctEzZ{ z^z(#Bq8{o?4D0ChsB{>HM~U7YfSsDO6z6Nn#2G#w( zN1}mkckse**4}3}iUhu)d*8J`DWsD5I{>DXYg`#=tcKetVDF-L(ffw7;F;JtH|vsDCa?8;x%6{$wVOUG+R%eqCFnLsh~ zp7D{mr%1-OLThotTH}vf9x=zt#K>3-ok{)gkAOe5s{`Rp*v;#n`*|185)sdpbW>dFB%^9$sR9=Zl<$)? z8N>s?)!cZnmD}V&z47FF@gxtwgtDpa_hr6sAv=%7tjUz$)`na#qP}3yMX z3_R9{1pRi~^)6TWRjHNH{o*mhj=WeBT}n8XoTMJa4=Jr+hiE_`cm60JNjApSdZZEI3eJ}&s55O3ckHB;c=*PwdHWzyj76n zXmz3M;d|mXKI*182Ky}r1kg69V0O@V4bZt|yt$;~RdWYW48@Q0Iq;j*zWPE>GQX?v zS8||sP2H&Ji2(G)K^li)uEZzENN4M1Z#reKz;EWt6DSvS{kEkTIl52Z2WtAvK>u-% zn0}oM2kOv20KOULyn#bXpt*s^nWuAdWN9`!T*Xvwt`s#>jhz^Xrf?LFlG&-5u(XA z`WH|{j-nhn2}QF|A)-IFhOidQ`fl7~{tI3I zBg_XhlBIvf1)AQ#3JP@!bQGisbN>xqIkpOl9sK{fYoWq4*91N;_m)7+NKGbtp6V(6`WbaAT-h|CwLS8)%|1qRWrUadabPqMt+8?O%9~ zO7sgff}bk{vnccTfxlDg$lFRm@mnvN5rOE0CnBRlp_l*UyBfmAQn3SV>@q6YD2~S3 z_xUGw(Xoc%Rai*ZE+iLYo4NtE5A3bsoXG{h;eo z)g*e#BQ7_r-Ci4RWAzerGRi>Z*CUp6GR3?N_MiFdd-v+>SB_T15=O>v)~O;a)=Rpp zt3+IrP5I8u`HsA8E|i{due;OT`)r&nbqO;YHN%nj5g?YQl-lA71W87o$-Z8;#Fe~l z5EJD&^v<4dr2}6-`qPKkBmoh$pR@c?&l1#``-iv7>6d;#D!idpN9)Jz<>jtlGez&! zdR_dWGW~kyw-LtM)Tzq-KKzHM-D+=1;}D?e_T_iuc>>Ce{W3!O9QvGZ8)bw;bt-T1 zCx(ExgN>tS=MKzUEY^e*a?mu0P<(n53n3xlrch+50Kb1tL-wE$Jg6r9K_Rs;V`U-#5q9D-%j0%|laU17uk$1!0mL3l)XdFQu zSPZ|4#CO)5x271*ZOu3d)ZBE98i{LxX74qId8MwDuQ<7ADvo^*zpy-N`kalCQ{xEG%CZPz?m2<(7*Vqcdxt2pn*i4z4gf|GQwi?9?PoG*mH+# zN$1Ie{(`);9!Eq^J?gUBN?f1VmLy2;dDjLza$YmL!-d4+OK-hVLj1gzwNX>2>r6eW z8#C*aHwe<}S%H%}M}QPkY0@&9C(&;p32w+#=57N11w~DD^VI4PoMT5tIjm=UxUnk| z7UY?@ep&G&=iDQjx5~ZWH%FT;d6Kh-4(giR6~~->H(^7!1jz*E7e$(;{v1|n0fPPM z{`kP;wusJy&;Z1_<-wvl`)9dQ^VS1iYOAvO*5dXY|Q zlDk`PLxPzXB#O)ZIi?E9T!C*pM_?Q~hg*_tjqk2MOOka{HEud!0JFXD?X~b$F-k74 z;rWT`gB;7WNUE9`yUZ#1YCSc2TMXY#uA=!Xysy(Fe%)fww~g=48d`zxpthHC%Cf~W HCZGNf0%Zst literal 0 HcmV?d00001 diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index 963a64a1bbf..534d4969782 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -70,7 +70,7 @@ def do_exit(*c): class JansCliApp(Application, JansAuthServer): - + def __init__(self): self.init_logger() self.status_bar_text = '' @@ -511,14 +511,14 @@ def edit_scope_dialog(self, **params): def show_message(self, title, message, buttons=[]): body = HSplit([Label(message)]) dialog = JansMessageDialog(title=title, body=body, buttons=buttons) - focused_before = self.root_layout.floats[-1].content if self.root_layout.floats else self.layout.current_window + + focused_before = self.layout.current_window float_ = Float(content=dialog) self.root_layout.floats.append(float_) dialog.me = float_ dialog.focus_on_exit = focused_before self.layout.focus(dialog) self.press_tab() - def show_again(self): ## nasted dialog Button self.show_message("Again", "Nasted Dialogs",) @@ -541,4 +541,4 @@ def run(): if __name__ == "__main__": - run() + run() \ No newline at end of file diff --git a/jans-cli-tui/mkdocs.yml b/jans-cli-tui/mkdocs.yml new file mode 100644 index 00000000000..0517cb649d3 --- /dev/null +++ b/jans-cli-tui/mkdocs.yml @@ -0,0 +1,55 @@ +site_name: Jans-Cli-Tui +site_url: 'https://gluu.org/gluu-4/' +repo_url: 'https://github.com/JanssenProject' +edit_uri: '#https://github.com/JanssenProject' +site_description: "this is desc" +plugins: + - mkdocstrings +nav: + - Home: 'docs/home/index.md' + - Gallery: + - 'CLI': 'docs/Gallery/cli.md' + - 'TUI': 'docs/Gallery/tui.md' + - Components: + - 'jans_cli_dialog': 'docs/wui_components/jans_cli_dialog.md' + - 'jans_data_picker': 'docs/wui_components/jans_data_picker.md' + - 'jans_dialog_with_nav': 'docs/wui_components/jans_dialog_with_nav.md' + - 'jans_drop_down': 'docs/wui_components/jans_drop_down.md' + - 'jans_message_dialog': 'docs/wui_components/jans_message_dialog.md' + - 'jans_nav_bar': 'docs/wui_components/jans_nav_bar.md' + - 'jans_side_nav_bar': 'docs/wui_components/jans_side_nav_bar.md' + - 'jans_vetrical_nav': 'docs/wui_components/jans_vetrical_nav.md' + + - TUI Main Components: + - 'Clinets': 'docs/Clinets/Clinets.md' + - 'Scopes': 'docs/Scopes/Scopes.md' + - 'Keys': 'docs/Keys/Keys.md' + - 'Defaults': 'docs/Defaults/Defaults.md' + - 'Properties': 'docs/Properties/Properties.md' + - 'Logging': 'docs/Logging/Logging.md' + - Clinets: + - 'Add Credintials': 'docs/Clinets/AddCredintials.md' + - 'Get Device Verification': 'docs/Clinets/Get.md' + - 'Add New Client': 'docs/Clinets/Add.md' + - 'Remove Client': 'docs/Clinets/Remove.md' + - 'Edit Client': 'docs/Clinets/Edit.md' + - 'Search Client': 'docs/Clinets/Search.md' + - Scopes: + - 'Scopes': 'docs/Scopes/Scopes.md' + - Keys: + - 'Keys': 'docs/Keys/Keys.md' + - Defaults: + - 'Defaults': 'docs/Defaults/Defaults.md' + - Properties: + - 'Properties': 'docs/Properties/Properties.md' + - Logging: + - 'Logging': 'docs/Logging/Logging.md' + - About: + - 'License': 'license.md' + - 'Release Notes': 'release-notes.md' +theme: + name: readthedocs + locale: en + include_sidebar: false +# theme: +# readthedocs diff --git a/jans-cli-tui/models/oauth/edit_client_dialog.py b/jans-cli-tui/models/oauth/edit_client_dialog.py index 30c2dd43287..de799b8ad2f 100644 --- a/jans-cli-tui/models/oauth/edit_client_dialog.py +++ b/jans-cli-tui/models/oauth/edit_client_dialog.py @@ -23,6 +23,12 @@ from utils import DialogUtils class EditClientDialog(JansGDialog, DialogUtils): + """_summary_ + + Args: + JansGDialog (_type_): _description_ + DialogUtils (_type_): _description_ + """ def __init__(self, parent, title, data, buttons=[], save_handler=None): super().__init__(parent, title, buttons) self.save_handler = save_handler diff --git a/jans-cli-tui/models/oauth/oauth.py b/jans-cli-tui/models/oauth/oauth.py index cdc1dc9bfbe..094fc6b5e8c 100644 --- a/jans-cli-tui/models/oauth/oauth.py +++ b/jans-cli-tui/models/oauth/oauth.py @@ -156,7 +156,7 @@ def oauth_update_clients(self, pattern=''): all_data=result ) - self.layout.focus(clients) # clients.focuse..!? TODO >> DONE + self.layout.focus(clients) self.oauth_data_container['clients'] = HSplit([ clients ]) diff --git a/jans-cli-tui/preview.ipynb b/jans-cli-tui/preview.ipynb new file mode 100644 index 00000000000..7f01e5468d2 --- /dev/null +++ b/jans-cli-tui/preview.ipynb @@ -0,0 +1,48 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Gallarey\n", + "demonstrating the possibilities of jans-tui.\n", + "\n", + "![Screenshot](docs/img/new_tui/new_tui1.PNG)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.8.10 64-bit", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.10" + }, + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/jans-cli-tui/wui_components/jans_cli_dialog.py b/jans-cli-tui/wui_components/jans_cli_dialog.py index 5803776976d..8c89150ba99 100644 --- a/jans-cli-tui/wui_components/jans_cli_dialog.py +++ b/jans-cli-tui/wui_components/jans_cli_dialog.py @@ -6,7 +6,21 @@ from prompt_toolkit.layout.dimension import D class JansGDialog: + """This is the main dialog Class Widget for all Jans-cli-tui dialogs except custom dialogs like dialogs with navbar + """ def __init__(self, parent, title, body, buttons=[], width=None): + """init for JansGDialog + + Args: + parent (widget): this is the parent widget for the dialog, to caluclate the size + title (String): the title for the dialog + body (Widget): The content of the dialog + buttons (list, optional): Dialog main buttons with their handlers. Defaults to []. + width (int, optional): If needed custom width. Defaults to None. + + Examples: + dialog = JansGDialog(self, title="Waiting Response", body=body) + """ self.future = Future() self.body = body self.myparent = parent diff --git a/jans-cli-tui/wui_components/jans_data_picker.py b/jans-cli-tui/wui_components/jans_data_picker.py index 8b674742150..5407bf35f6b 100644 --- a/jans-cli-tui/wui_components/jans_data_picker.py +++ b/jans-cli-tui/wui_components/jans_data_picker.py @@ -20,19 +20,23 @@ FloatContainer, Window ) - import calendar import time -# ts = time.strptime(x[:19], "%Y-%m-%dT%H:%M:%S") -# time.strftime("%m/%d/%Y", ts) -# '11/27/2023' -#### not finished yet >> data import datetime class JansSelectDate: + """_summary_ + """ def __init__(self, date='',months=[],mytime=[]): + """_summary_ + + Args: + date (str, optional): _description_. Defaults to ''. + months (list, optional): _description_. Defaults to []. + mytime (list, optional): _description_. Defaults to []. + """ self.hours , self.minuts , self.seconds = mytime self.change_date = True self.date = date #"11/27/2023" @@ -299,9 +303,15 @@ def __pt_container__(self): class DateSelectWidget: + """This is a Dape Picker widget to select exact time and date + """ def __init__(self, value): # ex: value = "2023-11-27T14:05:35" + """init for DateSelectWidget + + Args: + value (str): string time stamp value like "2023-11-27T14:05:35" + """ self.months = [calendar.month_name[i] for i in range(1,13)] - # text >> showed in the widget if value: self.text = value @@ -334,6 +344,11 @@ def __init__(self, value): # ex: value = "2023-11-27T14:05:35" @property def value(self): + """Getter for the value property + + Returns: + str: The selected value + """ if self.text != "Enter to Select": return self.text @@ -343,6 +358,11 @@ def value(self, value): self._value = self.value def make_time(self, text): + """extract time from the text to increase or decrease + + Args: + text (str): the text that appear on the wigdet + """ ts = time.strptime(text[:19], "%Y-%m-%dT%H:%M:%S") # "2023-11-27" years =int(time.strftime("%Y",ts)) months = int(time.strftime("%m",ts)) @@ -356,11 +376,23 @@ def make_time(self, text): self.text= (time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(t))) def _get_text(self): + """To get The selected value + + Returns: + str: The selected value + """ + if get_app().layout.current_window is self.window: return HTML('> <'.format('#00FF00', self.text)) return '> {} <'.format(self.text) def _get_key_bindings(self): + """All key binding for the Dialog with Navigation bar + + Returns: + KeyBindings: The method according to the binding key + """ + kb = KeyBindings() def _focus_next(event): diff --git a/jans-cli-tui/wui_components/jans_dialog.py b/jans-cli-tui/wui_components/jans_dialog.py index 91c7d5736ec..5732364af7e 100644 --- a/jans-cli-tui/wui_components/jans_dialog.py +++ b/jans-cli-tui/wui_components/jans_dialog.py @@ -6,6 +6,8 @@ class JansDialog(): + """NOt Used + """ def __init__(self,only_view=False,height=None,width=None,title=None, button_functions=[], entries_list=[], entries_color='#00ff44'): self.entries_list = entries_list self.button_functions = button_functions diff --git a/jans-cli-tui/wui_components/jans_dialog_with_nav.py b/jans-cli-tui/wui_components/jans_dialog_with_nav.py index 575d2f3b59e..2e0cae56624 100644 --- a/jans-cli-tui/wui_components/jans_dialog_with_nav.py +++ b/jans-cli-tui/wui_components/jans_dialog_with_nav.py @@ -19,7 +19,32 @@ class JansDialogWithNav(): + """This is a custom dialog Widget with side Navigation Bar (Used for Client/Scope dialogs) + """ def __init__(self,content, height=None, width=None, title=None, button_functions=[], navbar=None): + """init for JansDialogWithNav + + Args: + content (OrderedDict): All tabs content orderd in Dict + height (int, optional): Only if custom hieght is needed. Defaults to None. + width (int, optional): Only if custom width is needed. Defaults to None. + title (str, optional): The main title of the dialog. Defaults to None. + button_functions (list, optional): Dialog main buttons with their handlers. Defaults to []. + navbar (widget, optional): The Navigation bar widget can be Vertical (Side) Navigation bar or horizontal Navigation bar . Defaults to None. + + Examples: + self.dialog = JansDialogWithNav( + title=title, ## Dialog Title + navbar=self.side_nav_bar, ## Nav Bar widget + content=DynamicContainer(lambda: self.tabs[self.left_nav]), + button_functions=[ + (save, "Save"), ## Button Handler , Button Name + (cancel, "Cancel") ## Button Handler , Button Name + ], + height=10, ## Fixed Height + width=30, ## Fixed Width + ) + """ self.navbar = navbar self.button_functions = button_functions self.title = title @@ -29,7 +54,10 @@ def __init__(self,content, height=None, width=None, title=None, button_functions self.create_window() def create_window(self): - + """This method creat the dialog it self + Todo: + * Change `max_data_str` to be dynamic + """ max_data_str = 30 ## TODO TO BE Dynamic wwidth, wheight = get_terminal_size() @@ -56,9 +84,13 @@ def create_window(self): with_background=False, ) -#--------------------------------------------------------------------------------------# def get_nav_bar_key_bindings(self): + """All key binding for the Dialog with Navigation bar + + Returns: + KeyBindings: The method according to the binding key + """ kb = KeyBindings() @kb.add("pageup", eager=True) ### eager neglect any other keybinding diff --git a/jans-cli-tui/wui_components/jans_drop_down.py b/jans-cli-tui/wui_components/jans_drop_down.py index 538e67255b5..aa2c4d16111 100644 --- a/jans-cli-tui/wui_components/jans_drop_down.py +++ b/jans-cli-tui/wui_components/jans_drop_down.py @@ -9,7 +9,18 @@ from prompt_toolkit.layout.dimension import D class JansSelectBox: + """_summary_ + """ def __init__(self, values=[], value=None, height=4, rotatable_up=True, rotatable_down=True): + """_summary_ + + Args: + values (list, optional): _description_. Defaults to []. + value (_type_, optional): _description_. Defaults to None. + height (int, optional): _description_. Defaults to 4. + rotatable_up (bool, optional): _description_. Defaults to True. + rotatable_down (bool, optional): _description_. Defaults to True. + """ self.values = values self.set_value(value) @@ -34,6 +45,11 @@ def __init__(self, values=[], value=None, height=4, rotatable_up=True, rotatable )]) def set_value(self, value): + """_summary_ + + Args: + value (_type_): _description_ + """ self.value = value for i, val in enumerate(self.values): @@ -43,9 +59,12 @@ def set_value(self, value): else: self.selected_line = 0 - - def _get_formatted_text(self): + """_summary_ + + Returns: + _type_: _description_ + """ result = [] for i, entry in enumerate(self.values): if i == self.selected_line: @@ -56,11 +75,21 @@ def _get_formatted_text(self): return merge_formatted_text(result) - def shift(self,seq, n): + """_summary_ + + Args: + seq (_type_): _description_ + n (_type_): _description_ + + Returns: + _type_: _description_ + """ return seq[n:]+seq[:n] def up(self): + """_summary_ + """ if self.selected_line == 0 : if self.rotatable_up and self.values[self.selected_line] == self.values[0]: pass @@ -71,6 +100,8 @@ def up(self): def down(self): + """_summary_ + """ if self.selected_line +1 == (self.height): if self.rotatable_down and self.values[self.selected_line] == self.values[-1]: @@ -86,7 +117,19 @@ def __pt_container__(self): class DropDownWidget: + """This is a Combobox widget (drop down) to select single from multi choices + """ def __init__(self, values=[], value=None): + """init for DropDownWidget + Args: + values (list, optional): List of values to select one from them. Defaults to []. + value (str, optional): The defult selected value. Defaults to None. + + Examples: + widget=DropDownWidget( + values=[('client_secret_basic', 'client_secret_basic'), ('client_secret_post', 'client_secret_post'), ('client_secret_jwt', 'client_secret_jwt'), ('private_key_jwt', 'private_key_jwt')], + value=self.data.get('tokenEndpointAuthMethodsSupported')) + """ self.values = values for val in values: if val[0] == value: @@ -108,23 +151,34 @@ def __init__(self, values=[], value=None): @property def value(self): + """Getter for the value property + + Returns: + str: The selected value + """ return self.select_box.value - @value.setter def value(self, value): self.select_box.set_value(value) - def _get_text(self): + """To get The selected value + + Returns: + str: The selected value + """ if get_app().layout.current_window is self.window: return HTML('> <'.format('#00FF00', self.text)) return '> {} <'.format(self.text) - def _get_key_bindings(self): - kb = KeyBindings() + """All key binding for the Dialog with Navigation bar + Returns: + KeyBindings: The method according to the binding key + """ + kb = KeyBindings() def _focus_next(event): focus_next(event) diff --git a/jans-cli-tui/wui_components/jans_message_dialog.py b/jans-cli-tui/wui_components/jans_message_dialog.py index 25e6a032680..d1282dc4d2f 100644 --- a/jans-cli-tui/wui_components/jans_message_dialog.py +++ b/jans-cli-tui/wui_components/jans_message_dialog.py @@ -6,7 +6,21 @@ class JansMessageDialog: + """This is a Dialog to show Message + """ def __init__(self, title, body, buttons=[], focus_on_exit=None): + """init for JansMessageDialog + + Args: + title (str): The title of the Dialog message + body (widget): Widget to be displayed with the dialog (Usually Label) + buttons (list, optional): Dialog main buttons with their handlers. Defaults to []. + focus_on_exit (widget, optional): Move the focus on exit. Defaults to None. + + Examples: + buttons = [Button("OK", handler=my_method] + dialog = JansMessageDialog(title="my title", body=HSplit([Label(message)]), buttons=buttons) + """ self.result = None self.me = None self.focus_on_exit = focus_on_exit @@ -22,10 +36,10 @@ def exit_me(result, handler): if self.me in app.root_layout.floats: app.root_layout.floats.remove(self.me) - #try: - app.layout.focus(self.focus_on_exit) - #except: - # pass + try: + app.layout.focus(self.focus_on_exit) + except: + pass blist = [] diff --git a/jans-cli-tui/wui_components/jans_nav_bar.py b/jans-cli-tui/wui_components/jans_nav_bar.py index 31829830a98..dff6e12dbc4 100644 --- a/jans-cli-tui/wui_components/jans_nav_bar.py +++ b/jans-cli-tui/wui_components/jans_nav_bar.py @@ -5,7 +5,27 @@ class JansNavBar(): + """This is a horizontal Navigation bar Widget used in Main screen ('clients', 'scopes', 'keys', 'defaults', 'properties', 'logging') + """ def __init__(self, myparent, entries, selection_changed, select=0, bgcolor='#00ff44'): + """init for JansNavBar + + Args: + myparent (_type_): _description_ + entries (_type_): _description_ + selection_changed (_type_): _description_ + select (int, optional): _description_. Defaults to 0. + bgcolor (str, optional): _description_. Defaults to '#00ff44'. + + Examples: + self.oauth_navbar = JansNavBar( + self, + entries=[('clients', 'Clients'), ('scopes', 'Scopes'), ('keys', 'Keys'), ('defaults', 'Defaults'), ('properties', 'Properties'), ('logging', 'Logging')], + selection_changed=self.oauth_nav_selection_changed, + select=0, + bgcolor='#66d9ef' + ) + """ self.myparent = myparent self.navbar_entries = entries self.cur_navbar_selection = select @@ -14,8 +34,9 @@ def __init__(self, myparent, entries, selection_changed, select=0, bgcolor='#00f self.cur_tab = entries[self.cur_navbar_selection][0] self.create_window() - def create_window(self): + """This method creat the Navigation Bar it self + """ self.nav_window = Window( content=FormattedTextControl( text=self.get_navbar_entries, @@ -27,6 +48,11 @@ def create_window(self): ) def get_navbar_entries(self): + """Get all selective entries + + Returns: + merge_formatted_text: Merge (Concatenate) several pieces of formatted text together. + """ result = [] for i, entry in enumerate(self.navbar_entries): @@ -38,8 +64,12 @@ def get_navbar_entries(self): return merge_formatted_text(result) - def get_nav_bar_key_bindings(self): + """All key binding for the Dialog with Navigation bar + + Returns: + KeyBindings: The method according to the binding key + """ kb = KeyBindings() @kb.add("left") diff --git a/jans-cli-tui/wui_components/jans_side_nav_bar.py b/jans-cli-tui/wui_components/jans_side_nav_bar.py index 108657081be..5cc22dcbc83 100644 --- a/jans-cli-tui/wui_components/jans_side_nav_bar.py +++ b/jans-cli-tui/wui_components/jans_side_nav_bar.py @@ -10,7 +10,25 @@ class JansSideNavBar(): + """This is a Vertical Navigation bar Widget with one value used in clients/scopes dialogs + """ def __init__(self, myparent, entries, selection_changed, select=0, entries_color='#00ff44'): + """init for JansSideNavBar + + Args: + parent (widget): This is the parent widget for the dialog, to caluclate the size + entries (list): List of all navigation headers names + selection_changed (method): Method to be invoked when selection is changed + select (int, optional): The first value to be selected. Defaults to 0. + entries_color (str, optional): Color for entries. Defaults to '#00ff44'. + + Examples: + self.side_nav_bar = JansSideNavBar(myparent=self.myparent, + entries=list(self.tabs.keys()), + selection_changed=(self.client_dialog_nav_selection_changed) , + select=0, + entries_color='#2600ff') + """ self.myparent = myparent # ListBox parent class self.navbar_entries = entries # ListBox entries self.cur_navbar_selection = select # ListBox initial selection @@ -20,6 +38,10 @@ def __init__(self, myparent, entries, selection_changed, select=0, entries_color self.create_window() def create_window(self): + """This method creat the dialog it self + Todo: + * Change `width` to be dynamic + """ self.side_nav = FloatContainer( content=HSplit([ Window( @@ -39,11 +61,21 @@ def create_window(self): ] ), floats=[] ) -#--------------------------------------------------------------------------------------# + def get_data_width(self): + """get the largest title lenght + + Returns: + int: the max title lenght + """ return len(max(self.navbar_entries, key=len)) -#--------------------------------------------------------------------------------------# + def get_navbar_entries(self): + """Get all selective entries + + Returns: + merge_formatted_text: Merge (Concatenate) several pieces of formatted text together. + """ result = [] for i, entry in enumerate(self.navbar_entries): @@ -54,23 +86,28 @@ def get_navbar_entries(self): result.append("\n") return merge_formatted_text(result) - def update_selection(self): + """Update the selected tab and pass the current tab name to the selection_changed handler + """ self.cur_tab = self.navbar_entries[self.cur_navbar_selection] self.selection_changed(self.cur_tab) + # def go_up(self): + # self.cur_navbar_selection = ( + # self.cur_navbar_selection - 1) % len(self.navbar_entries) + # self.update_selection() - def go_up(self): - self.cur_navbar_selection = ( - self.cur_navbar_selection - 1) % len(self.navbar_entries) - self.update_selection() - - def go_down(self): - self.cur_navbar_selection = ( - self.cur_navbar_selection + 1) % len(self.navbar_entries) - self.update_selection() + # def go_down(self): + # self.cur_navbar_selection = ( + # self.cur_navbar_selection + 1) % len(self.navbar_entries) + # self.update_selection() def get_nav_bar_key_bindings(self): + """All key binding for the Dialog with Navigation bar + + Returns: + KeyBindings: The method according to the binding key + """ kb = KeyBindings() @kb.add("up") @@ -80,16 +117,14 @@ def _go_up(event) -> None: self.update_selection() @kb.add("down") - def _go_up(event) -> None: + def _go_down(event) -> None: self.cur_navbar_selection = ( self.cur_navbar_selection + 1) % len(self.navbar_entries) self.update_selection() - - - @kb.add("enter") - def _(event): - pass + # @kb.add("enter") + # def _(event): + # pass return kb diff --git a/jans-cli-tui/wui_components/jans_vetrical_nav.py b/jans-cli-tui/wui_components/jans_vetrical_nav.py index da4a76707cd..a4e96740a32 100644 --- a/jans-cli-tui/wui_components/jans_vetrical_nav.py +++ b/jans-cli-tui/wui_components/jans_vetrical_nav.py @@ -7,10 +7,44 @@ class JansVerticalNav(): + """This is a Vertical Navigation bar Widget with many values used in / + """ def __init__(self, myparent, headers, selectes, on_enter, on_display, on_delete=None, all_data=None, preferred_size=[], data=None, headerColor='green', entriesColor='white'): - + """init for JansVerticalNav + + Args: + parent (widget): This is the parent widget for the dialog, to caluclate the size + headers (List): List of all navigation headers names + selectes (int): The first value to be selected. + on_enter (Method): Method to be called when '' key is pressed + on_display (Method): Method to be called when '' key is pressed + on_delete (Method, optional): Method to be called when '' key is pressed. Defaults to None. + all_data (List, optional): All Data to be used with `on_enter` and `on_display`. Defaults to None. + preferred_size (list, optional): List of the max desired width for the columns contents. Defaults to []. + data (List, optional): Data to be displayed. Defaults to None. + headerColor (str, optional): Color for the Headers. Defaults to 'green'. + entriesColor (str, optional): Color for the Entries. Defaults to 'white'. + + Examples: + clients = JansVerticalNav( + myparent=self, + headers=['Client ID', 'Client Name', 'Grant Types', 'Subject Type'], + preferred_size= [0,0,30,0], + data=data, + on_enter=self.edit_client_dialog, + on_display=self.data_display_dialog, + on_delete=self.delete_client, + # selection_changed=self.data_selection_changed, + selectes=0, + headerColor='green', + entriesColor='white', + all_data=result + ) + Todo: + * Needs refactor + """ self.myparent = myparent # ListBox parent class self.headers = headers # ListBox headers self.selectes = selectes # ListBox initial selection @@ -47,7 +81,8 @@ def view_data(self): self.mod_data = result def create_window(self): - + """This method creat the dialog it self + """ self.container = FloatContainer( content=HSplit([ Window( @@ -83,6 +118,8 @@ def create_window(self): ) def handle_header_spaces(self): + """Make header evenlly spaced + """ datalen = [] for dataline in range(len(self.mod_data )): line = [] @@ -107,9 +144,10 @@ def handle_header_spaces(self): (len(self.headers[i]) - self.spaces[i]) self.spaces[-1] = self.myparent.output.get_size()[1] - sum(self.spaces) + sum(len(s) for s in self.headers) ## handle last head spaces (add space to the end of ter. width to remove the white line) - # -------------------------------------------------------------------------------- # - # -------------------------------------------------------------------------------- # + def handle_data_spaces(self): + """Make entries evenlly spaced + """ for i in range(len(self.mod_data)): for k in range(len(self.spaces)): if len(self.mod_data[i][k]) != self.spaces[k]: @@ -119,6 +157,12 @@ def handle_data_spaces(self): pass def _get_head_text(self): + """Get all headers entries + + Returns: + merge_formatted_text: Merge (Concatenate) several pieces of formatted text together. + """ + result = [] y = '' for k in range(len(self.headers)): @@ -130,6 +174,11 @@ def _get_head_text(self): return merge_formatted_text(result) def _get_formatted_text(self): + """Get all selective entries + + Returns: + merge_formatted_text: Merge (Concatenate) several pieces of formatted text together. + """ result = [] for i, entry in enumerate(self.mod_data): ## entry = ['1800.6c5faa', 'Jans Config Api Client', 'authorization_code,refresh_...', 'Reference] if i == self.selectes: @@ -140,9 +189,12 @@ def _get_formatted_text(self): return merge_formatted_text(result) - # -------------------------------------------------------------------------------- # - # -------------------------------------------------------------------------------- # def _get_key_bindings(self): + """All key binding for the Dialog with Navigation bar + + Returns: + KeyBindings: The method according to the binding key + """ kb = KeyBindings() @kb.add("up") @@ -182,8 +234,5 @@ def _(event): return kb - # -------------------------------------------------------------------------------- # - # -------------------------------------------------------------------------------- # - def __pt_container__(self): return self.container From d0d526a29324d7afc659c50f2bef19d399237652 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Thu, 1 Sep 2022 15:42:50 +0300 Subject: [PATCH 094/364] refactor: jans-cli models as plugin --- jans-cli-tui/jans-cli-tui.py | 59 ++++++---- jans-cli-tui/models/__init__.py | 0 .../010_oxauth}/edit_client_dialog.py | 0 .../010_oxauth}/edit_scope_dialog.py | 0 .../oauth.py => plugins/010_oxauth/main.py} | 101 ++++++++++-------- jans-cli-tui/plugins/020_fido/main.py | 21 ++++ jans-cli-tui/plugins/030_scim/main.py | 21 ++++ jans-cli-tui/plugins/040_config_api/main.py | 21 ++++ jans-cli-tui/plugins/050_client_api/main.py | 21 ++++ jans-cli-tui/plugins/060_scripts/main.py | 21 ++++ jans-cli-tui/wui_components/jans_nav_bar.py | 10 ++ 11 files changed, 209 insertions(+), 66 deletions(-) delete mode 100644 jans-cli-tui/models/__init__.py rename jans-cli-tui/{models/oauth => plugins/010_oxauth}/edit_client_dialog.py (100%) rename jans-cli-tui/{models/oauth => plugins/010_oxauth}/edit_scope_dialog.py (100%) rename jans-cli-tui/{models/oauth/oauth.py => plugins/010_oxauth/main.py} (70%) create mode 100644 jans-cli-tui/plugins/020_fido/main.py create mode 100644 jans-cli-tui/plugins/030_scim/main.py create mode 100644 jans-cli-tui/plugins/040_config_api/main.py create mode 100644 jans-cli-tui/plugins/050_client_api/main.py create mode 100644 jans-cli-tui/plugins/060_scripts/main.py diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index 534d4969782..75b145ab5ca 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -1,11 +1,13 @@ #!/usr/bin/env python3 """ """ -import json import os +import json +import time import logging +import importlib -import time +from pathlib import Path from asyncio import Future, ensure_future from pynput.keyboard import Key, Controller @@ -48,8 +50,6 @@ from cli_style import style -from models.oauth.oauth import JansAuthServer -from pathlib import Path # -------------------------------------------------------------------------- # @@ -57,6 +57,7 @@ config_dir = home_dir.joinpath('.config') config_dir.mkdir(parents=True, exist_ok=True) config_ini_fn = config_dir.joinpath('jans-cli.ini') +cur_dir = os.path.dirname(os.path.realpath(__file__)) def accept_yes(): get_app().exit(result=True) @@ -69,16 +70,22 @@ def do_exit(*c): get_app().exit(result=False) -class JansCliApp(Application, JansAuthServer): +class JansCliApp(Application): def __init__(self): self.init_logger() self.status_bar_text = '' self.styles = dict(style.style_rules) - + self._plugins = [] + self._load_plugins() self.set_keybindings() # -------------------------------------------------------------------------------- # + self.not_implemented = Frame( + body=HSplit([Label(text="Not imlemented yet"), Button(text="MyButton")], width=D()), + height=D()) + + self.keyboard = Controller() self.yes_button = Button(text="Yes", handler=accept_yes) @@ -87,18 +94,13 @@ def __init__(self): FormattedTextControl(self.update_status_bar), style="class:status", height=1 ) - JansAuthServer.initialize(self) - - - self.not_implemented = Frame( - body=HSplit([Label(text="Not imlemented yet"), Button(text="MyButton")], width=D()), - height=D()) - self.center_container = self.not_implemented self.nav_bar = JansNavBar( self, - entries=[('oauth', 'Auth Server'), ('fido', 'FDIO'), ('scim', 'SCIM'), ('config_api', 'Config-API'), ('client_api', 'Client-API'), ('scripts', 'Scripts')], + entries=[(plugin.pid, plugin.name) for plugin in self._plugins], + #('oauth', 'Auth Server'), ('fido', 'FDIO'), ('scim', 'SCIM'), ('config_api', 'Config-API'), ('client_api', 'Client-API'), ('scripts', 'Scripts')], + #selection_changed=self.main_nav_selection_changed, selection_changed=self.main_nav_selection_changed, select=0, ) @@ -133,13 +135,22 @@ def __init__(self): self.main_nav_selection_changed(self.nav_bar.navbar_entries[0][0]) # Since first module is oauth, set center frame to my oauth main container. - self.oauth_set_center_frame() + #self.oauth_set_center_frame() # ----------------------------------------------------------------------------- # self.check_jans_cli_ini() # ----------------------------------------------------------------------------- # + def _load_plugins(self): + + plugin_dir = os.path.join(cur_dir, 'plugins') + for plugin_file in sorted(Path(plugin_dir).glob('*/main.py')): + spec = importlib.util.spec_from_file_location(plugin_file.stem, plugin_file.as_posix()) + plugin = importlib.util.module_from_spec(spec) + spec.loader.exec_module(plugin) + self._plugins.append(plugin.Plugin(self)) + @property def dialog_width(self): return int(self.output.get_size().rows*0.8) @@ -152,7 +163,6 @@ def init_logger(self): self.logger = logging.getLogger('JansCli') self.logger.setLevel(logging.DEBUG) formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') - cur_dir = os.path.dirname(os.path.realpath(__file__)) file_handler = logging.FileHandler(os.path.join(cur_dir, 'dev.log')) file_handler.setLevel(logging.DEBUG) file_handler.setFormatter(formatter) @@ -434,12 +444,17 @@ def update_status_bar(self): return text + def get_plugin_by_id(self, pid): + for plugin in self._plugins: + if plugin.pid == pid: + return plugin + + def main_nav_selection_changed(self, selection): - if hasattr(self, selection+'_set_center_frame'): - center_frame_setter = getattr(self, selection+'_set_center_frame') - center_frame_setter() - else: - self.center_container = self.not_implemented + self.logger.debug("Main navbar selection changed %s", str(selection)) + plugin = self.get_plugin_by_id(selection) + plugin.set_center_frame() + async def show_dialog_as_float(self, dialog): "Coroutine." @@ -541,4 +556,4 @@ def run(): if __name__ == "__main__": - run() \ No newline at end of file + run() diff --git a/jans-cli-tui/models/__init__.py b/jans-cli-tui/models/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/jans-cli-tui/models/oauth/edit_client_dialog.py b/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py similarity index 100% rename from jans-cli-tui/models/oauth/edit_client_dialog.py rename to jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py diff --git a/jans-cli-tui/models/oauth/edit_scope_dialog.py b/jans-cli-tui/plugins/010_oxauth/edit_scope_dialog.py similarity index 100% rename from jans-cli-tui/models/oauth/edit_scope_dialog.py rename to jans-cli-tui/plugins/010_oxauth/edit_scope_dialog.py diff --git a/jans-cli-tui/models/oauth/oauth.py b/jans-cli-tui/plugins/010_oxauth/main.py similarity index 70% rename from jans-cli-tui/models/oauth/oauth.py rename to jans-cli-tui/plugins/010_oxauth/main.py index 094fc6b5e8c..c073c9cce86 100644 --- a/jans-cli-tui/models/oauth/oauth.py +++ b/jans-cli-tui/plugins/010_oxauth/main.py @@ -1,3 +1,6 @@ +import os +import sys + import threading from asyncio import ensure_future @@ -16,6 +19,7 @@ Box, Button, Label, + Frame ) from cli import config_cli @@ -25,19 +29,32 @@ from wui_components.jans_dialog import JansDialog from wui_components.jans_dialog_with_nav import JansDialogWithNav from wui_components.jans_drop_down import DropDownWidget -from models.oauth.edit_client_dialog import EditClientDialog -from models.oauth.edit_scope_dialog import EditScopeDialog from wui_components.jans_data_picker import DateSelectWidget +sys.path.append(os.path.dirname(os.path.realpath(__file__))) + +from edit_client_dialog import EditClientDialog +from edit_scope_dialog import EditScopeDialog + -class JansAuthServer: +class Plugin(): + def __init__(self, app): + self.app = app + self.pid = 'oxauth' + self.name = 'Auth Server' - def initialize(self): self.oauth_containers = {} self.oauth_prepare_navbar() self.oauth_prepare_containers() self.oauth_nav_selection_changed(self.oauth_navbar.navbar_entries[0][0]) + def process(self): + pass + + def set_center_frame(self): + self.app.center_container = self.oauth_main_container + + def oauth_prepare_containers(self): self.oauth_data_container = { @@ -49,9 +66,9 @@ def oauth_prepare_containers(self): self.oauth_containers['scopes'] = HSplit([ VSplit([ - self.getButton(text="Get Scopes", name='oauth:scopes:get', jans_help="Retreive first 10 Scopes", handler=self.oauth_get_scopes), - self.getTitledText('Search: ', name='oauth:scopes:search', jans_help='Press enter to perform search'), - self.getButton(text="Add Scope", name='oauth:scopes:add', jans_help="To add a new scope press this button"), + self.app.getButton(text="Get Scopes", name='oauth:scopes:get', jans_help="Retreive first 10 Scopes", handler=self.oauth_get_scopes), + self.app.getTitledText('Search: ', name='oauth:scopes:search', jans_help='Press enter to perform search'), + self.app.getButton(text="Add Scope", name='oauth:scopes:add', jans_help="To add a new scope press this button"), ], padding=3, width=D(), @@ -61,9 +78,9 @@ def oauth_prepare_containers(self): self.oauth_containers['clients'] = HSplit([ VSplit([ - self.getButton(text="Get Clients", name='oauth:clients:get', jans_help="Retreive first 10 OpenID Connect clients", handler=self.oauth_get_clients), - self.getTitledText('Search', name='oauth:clients:search', jans_help='Press enter to perform search', accept_handler=self.search_clients), - self.getButton(text="Add Client", name='oauth:clients:add', jans_help="To add a new client press this button", handler=self.add_client), + self.app.getButton(text="Get Clients", name='oauth:clients:get', jans_help="Retreive first 10 OpenID Connect clients", handler=self.oauth_get_clients), + self.app.getTitledText('Search', name='oauth:clients:search', jans_help='Press enter to perform search', accept_handler=self.search_clients), + self.app.getButton(text="Add Client", name='oauth:clients:add', jans_help="To add a new client press this button", handler=self.add_client), ], padding=3, @@ -90,13 +107,12 @@ def oauth_prepare_navbar(self): ) def oauth_nav_selection_changed(self, selection): + self.app.logger.debug("OXUATH NAV: %s", selection) if selection in self.oauth_containers: self.oauth_main_area = self.oauth_containers[selection] else: - self.oauth_main_area = self.not_implemented + self.oauth_main_area = self.app.not_implemented - def oauth_set_center_frame(self): - self.center_container = self.oauth_main_container def oauth_update_clients(self, pattern=''): endpoint_args='limit:10' @@ -104,7 +120,7 @@ def oauth_update_clients(self, pattern=''): endpoint_args='limit:10,pattern:'+pattern try : - rsponse = self.cli_object.process_command_by_id( + rsponse = self.app.cli_object.process_command_by_id( operation_id='get-oauth-openid-clients', url_suffix='', endpoint_args=endpoint_args, @@ -113,17 +129,17 @@ def oauth_update_clients(self, pattern=''): ) except Exception as e: - self.show_message("Error getting clients", str(e)) + self.app.show_message("Error getting clients", str(e)) return if rsponse.status_code not in (200, 201): - self.show_message("Error getting clients", str(rsponse.text)) + self.app.show_message("Error getting clients", str(rsponse.text)) return try: result = rsponse.json() except Exception: - self.show_message("Error getting clients", str(rsponse.text)) + self.app.show_message("Error getting clients", str(rsponse.text)) #press_tab return @@ -142,12 +158,12 @@ def oauth_update_clients(self, pattern=''): if data: clients = JansVerticalNav( - myparent=self, + myparent=self.app, headers=['Client ID', 'Client Name', 'Grant Types', 'Subject Type'], preferred_size= [0,0,30,0], data=data, on_enter=self.edit_client_dialog, - on_display=self.data_display_dialog, + on_display=self.app.data_display_dialog, on_delete=self.delete_client, # selection_changed=self.data_selection_changed, selectes=0, @@ -156,14 +172,14 @@ def oauth_update_clients(self, pattern=''): all_data=result ) - self.layout.focus(clients) + self.app.layout.focus(clients) self.oauth_data_container['clients'] = HSplit([ clients ]) get_app().invalidate() else: - self.show_message("Oops", "No matching result") + self.app.show_message("Oops", "No matching result") def oauth_get_clients(self): @@ -173,7 +189,7 @@ def oauth_get_clients(self): def update_oauth_scopes(self, start_index=0): try : - result = self.cli_object.process_command_by_id('get-oauth-scopes', '', 'limit:10', {}) + result = self.app.cli_object.process_command_by_id('get-oauth-scopes', '', 'limit:10', {}) data =[] for d in result: data.append( @@ -228,22 +244,21 @@ def edit_scope(self, selected,event,size): ## enter self.edit_scope_dialog() def edit_client_dialog(self, **params): - selected_line_data = params['data'] title = "Edit user Data (Clients)" - self.logger.debug("START") - self.logger.debug(selected_line_data) - self.logger.debug("END") + self.app.logger.debug("START") + self.app.logger.debug(selected_line_data) + self.app.logger.debug("END") - dialog = EditClientDialog(self, title=title, data=selected_line_data, save_handler=self.save_client) - self.show_jans_dialog(dialog) + dialog = EditClientDialog(self.app, title=title, data=selected_line_data, save_handler=self.save_client) + self.app.show_jans_dialog(dialog) def save_client(self, dialog): - self.logger.debug(dialog.data) + self.app.logger.debug(dialog.data) - response = self.cli_object.process_command_by_id( + response = self.app.cli_object.process_command_by_id( operation_id='put-oauth-openid-clients' if dialog.data.get('inum') else 'post-oauth-openid-clients', url_suffix='', endpoint_args='', @@ -251,44 +266,42 @@ def save_client(self, dialog): data=dialog.data ) - self.logger.debug(response.text) + self.app.logger.debug(response.text) if response.status_code in (200, 201): self.oauth_get_clients() return True - self.show_message("Error!", "An error ocurred while saving client:\n" + str(response.text)) + self.app.show_message("Error!", "An error ocurred while saving client:\n" + str(response.text)) def search_clients(self, tbuffer): if not len(tbuffer.text) > 2: - self.show_message("Error!", "Search string should be at least three characters") + self.app.show_message("Error!", "Search string should be at least three characters") return t = threading.Thread(target=self.oauth_update_clients, args=(tbuffer.text,), daemon=True) t.start() - def add_client(self): - dialog = EditClientDialog(self, title="Add Client", data={}, save_handler=self.save_client) - result = self.show_jans_dialog(dialog) + dialog = EditClientDialog(self.app, title="Add Client", data={}, save_handler=self.save_client) + result = self.app.show_jans_dialog(dialog) def delete_client(self, selected, event): - dialog = self.get_confirm_dialog("Are you sure want to delete client inum:\n {} ?".format(selected[0])) + dialog = self.app.get_confirm_dialog("Are you sure want to delete client inum:\n {} ?".format(selected[0])) async def coroutine(): - app = get_app() - focused_before = app.layout.current_window - self.layout.focus(dialog) - result = await self.show_dialog_as_float(dialog) + focused_before = self.app.layout.current_window + self.app.layout.focus(dialog) + result = await self.app.show_dialog_as_float(dialog) try: - app.layout.focus(focused_before) + self.app.layout.focus(focused_before) except: - app.layout.focus(self.center_frame) + self.app.layout.focus(self.app.center_frame) if result.lower() == 'yes': - result = self.cli_object.process_command_by_id( + result = self.app.cli_object.process_command_by_id( operation_id='delete-oauth-openid-clients-by-inum', url_suffix='inum:{}'.format(selected[0]), endpoint_args='', diff --git a/jans-cli-tui/plugins/020_fido/main.py b/jans-cli-tui/plugins/020_fido/main.py new file mode 100644 index 00000000000..5faf5f5ee7c --- /dev/null +++ b/jans-cli-tui/plugins/020_fido/main.py @@ -0,0 +1,21 @@ +import os +import sys + +from prompt_toolkit.layout.containers import HSplit +from prompt_toolkit.layout.dimension import D +from prompt_toolkit.widgets import Button, Label, Frame + +class Plugin(): + def __init__(self, app): + self.app = app + self.pid = 'fido' + self.name = 'FIDO' + + def process(self): + pass + + def set_center_frame(self): + self.app.center_container = Frame( + body=HSplit([Label(text="Plugin {} is not imlemented yet".format(self.name)), Button(text="Button-{}".format(self.pid))], width=D()), + height=D()) + diff --git a/jans-cli-tui/plugins/030_scim/main.py b/jans-cli-tui/plugins/030_scim/main.py new file mode 100644 index 00000000000..d1f1627b87a --- /dev/null +++ b/jans-cli-tui/plugins/030_scim/main.py @@ -0,0 +1,21 @@ +import os +import sys + +from prompt_toolkit.layout.containers import HSplit +from prompt_toolkit.layout.dimension import D +from prompt_toolkit.widgets import Button, Label, Frame + +class Plugin(): + def __init__(self, app): + self.app = app + self.pid = 'scim' + self.name = 'SCIM' + + def process(self): + pass + + def set_center_frame(self): + self.app.center_container = Frame( + body=HSplit([Label(text="Plugin {} is not imlemented yet".format(self.name)), Button(text="Button-{}".format(self.pid))], width=D()), + height=D()) + diff --git a/jans-cli-tui/plugins/040_config_api/main.py b/jans-cli-tui/plugins/040_config_api/main.py new file mode 100644 index 00000000000..5f5c49babeb --- /dev/null +++ b/jans-cli-tui/plugins/040_config_api/main.py @@ -0,0 +1,21 @@ +import os +import sys + +from prompt_toolkit.layout.containers import HSplit +from prompt_toolkit.layout.dimension import D +from prompt_toolkit.widgets import Button, Label, Frame + +class Plugin(): + def __init__(self, app): + self.app = app + self.pid = 'config_api' + self.name = 'Config-API' + + def process(self): + pass + + def set_center_frame(self): + self.app.center_container = Frame( + body=HSplit([Label(text="Plugin {} is not imlemented yet".format(self.name)), Button(text="Button-{}".format(self.pid))], width=D()), + height=D()) + diff --git a/jans-cli-tui/plugins/050_client_api/main.py b/jans-cli-tui/plugins/050_client_api/main.py new file mode 100644 index 00000000000..24f3cc35cc0 --- /dev/null +++ b/jans-cli-tui/plugins/050_client_api/main.py @@ -0,0 +1,21 @@ +import os +import sys + +from prompt_toolkit.layout.containers import HSplit +from prompt_toolkit.layout.dimension import D +from prompt_toolkit.widgets import Button, Label, Frame + +class Plugin(): + def __init__(self, app): + self.app = app + self.pid = 'client_api' + self.name = 'Client-API' + + def process(self): + pass + + def set_center_frame(self): + self.app.center_container = Frame( + body=HSplit([Label(text="Plugin {} is not imlemented yet".format(self.name)), Button(text="Button-{}".format(self.pid))], width=D()), + height=D()) + diff --git a/jans-cli-tui/plugins/060_scripts/main.py b/jans-cli-tui/plugins/060_scripts/main.py new file mode 100644 index 00000000000..fd3d508fe2e --- /dev/null +++ b/jans-cli-tui/plugins/060_scripts/main.py @@ -0,0 +1,21 @@ +import os +import sys + +from prompt_toolkit.layout.containers import HSplit +from prompt_toolkit.layout.dimension import D +from prompt_toolkit.widgets import Button, Label, Frame + +class Plugin(): + def __init__(self, app): + self.app = app + self.pid = 'scripts' + self.name = 'Scripts' + + def process(self): + pass + + def set_center_frame(self): + self.app.center_container = Frame( + body=HSplit([Label(text="Plugin {} is not imlemented yet".format(self.name)), Button(text="Button-{}".format(self.pid))], width=D()), + height=D()) + diff --git a/jans-cli-tui/wui_components/jans_nav_bar.py b/jans-cli-tui/wui_components/jans_nav_bar.py index dff6e12dbc4..cd0a512de2a 100644 --- a/jans-cli-tui/wui_components/jans_nav_bar.py +++ b/jans-cli-tui/wui_components/jans_nav_bar.py @@ -64,6 +64,12 @@ def get_navbar_entries(self): return merge_formatted_text(result) + + def _set_selection(self): + + if self.selection_changed: + self.selection_changed(self.navbar_entries[self.cur_navbar_selection][0]) + def get_nav_bar_key_bindings(self): """All key binding for the Dialog with Navigation bar @@ -75,9 +81,13 @@ def get_nav_bar_key_bindings(self): @kb.add("left") def _go_up(event) -> None: self.cur_navbar_selection = (self.cur_navbar_selection - 1) % len(self.navbar_entries) + self._set_selection() @kb.add("right") def _go_up(event) -> None: self.cur_navbar_selection = (self.cur_navbar_selection + 1) % len(self.navbar_entries) + self._set_selection() + + return kb From 8d77066f63998b1e6f9d567cfdc4bf63b3ea7508 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Thu, 1 Sep 2022 23:31:03 -0700 Subject: [PATCH 095/364] feat:jans_cli add docString --- .../plugins/010_oxauth/edit_client_dialog.py | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py b/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py index de799b8ad2f..47dfe4a0944 100644 --- a/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py +++ b/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py @@ -23,13 +23,21 @@ from utils import DialogUtils class EditClientDialog(JansGDialog, DialogUtils): - """_summary_ - - Args: - JansGDialog (_type_): _description_ - DialogUtils (_type_): _description_ + """The Main Client Dialog that contain every thing related to The Client """ def __init__(self, parent, title, data, buttons=[], save_handler=None): + """init for `EditClientDialog`, inherits from two diffrent classes `JansGDialog` and `DialogUtils` + + JansGDialog (dialog): This is the main dialog Class Widget for all Jans-cli-tui dialogs except custom dialogs like dialogs with navbar + DialogUtils (methods): Responsable for all `make data from dialog` and `check required fields` in the form for any Edit or Add New + + Args: + parent (widget): _description_ + title (_type_): _description_ + data (_type_): _description_ + buttons (list, optional): _description_. Defaults to []. + save_handler (_type_, optional): _description_. Defaults to None. + """ super().__init__(parent, title, buttons) self.save_handler = save_handler self.data = data @@ -343,10 +351,10 @@ def allow_spontaneous_changed(cb): self.myparent.getTitledText("TLS Subject DN", name='x5c', value=self.data.get('x5c',''),style='green'), self.myparent.getTitledWidget( - "Client Experiation Date", + "Client Expiration Date", name='expirationDate', widget=DateSelectWidget( - value=self.data.get('expirationDate', "") + value=self.data.get('expirationDate', ""),parent=self ), jans_help=self.myparent.get_help_from_schema(schema, 'expirationDate'), style='green' From 6858e0809221fcdae06fef438f7645ee980d8830 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Thu, 1 Sep 2022 23:33:37 -0700 Subject: [PATCH 096/364] fix:jans_cli Display previously selected date in DatePicker --- .../wui_components/jans_data_picker.py | 76 +++++++++++-------- 1 file changed, 43 insertions(+), 33 deletions(-) diff --git a/jans-cli-tui/wui_components/jans_data_picker.py b/jans-cli-tui/wui_components/jans_data_picker.py index 5407bf35f6b..db91f710292 100644 --- a/jans-cli-tui/wui_components/jans_data_picker.py +++ b/jans-cli-tui/wui_components/jans_data_picker.py @@ -24,7 +24,6 @@ import time import datetime - class JansSelectDate: """_summary_ """ @@ -43,6 +42,7 @@ def __init__(self, date='',months=[],mytime=[]): self.months = months self.cord_y = 0 self.cord_x = 0 + self.old_cord_x = 0 self.selected_cord = (0, 0) self.extract_date(self.date) @@ -100,7 +100,6 @@ def __init__(self, date='',months=[],mytime=[]): ), ]) - def digitalize (self,number) : if len(str(number)) == 1 : return '0'+ str(number) @@ -190,7 +189,6 @@ def _get_calender_text(self): return merge_formatted_text(result) - def adjust_month(self, day, i): if self.change_date: if i == 1 and self.current_month == 12: @@ -207,7 +205,6 @@ def adjust_month(self, day, i): current_date = str(self.current_month)+'/'+str(day) + '/'+str(self.current_year) self.extract_date(current_date) - def inc_month(self,day): self.adjust_month(day, 1) @@ -220,14 +217,12 @@ def adjust_year(self, day, i): current_date = str(day)+'/'+str(self.current_month) + '/'+str(self.current_year) self.extract_date(current_date)# 20/2/1997 - def inc_year(self, day): self.adjust_year(day, 1) def dec_year(self, day): self.adjust_year(day, -1) - def adjust_time(self, i): if self.cord_x ==0 : self.hours +=i @@ -262,7 +257,6 @@ def down(self): else: self.adjust_time(-1) - def right(self): if self.change_date: if self.cord_x == 6 or int(self.entries[self.cord_y][self.cord_x+1]) == 0: @@ -294,23 +288,27 @@ def left(self): def next(self): self.change_date = not self.change_date + if not self.change_date: + self.old_cord_x = self.cord_x self.cord_x = 0 + else: + self.cord_x = self.old_cord_x def __pt_container__(self): return self.container - class DateSelectWidget: """This is a Dape Picker widget to select exact time and date """ - def __init__(self, value): # ex: value = "2023-11-27T14:05:35" + def __init__(self, value,parent): # ex: value = "2023-11-27T14:05:35" """init for DateSelectWidget Args: value (str): string time stamp value like "2023-11-27T14:05:35" """ + self.parent = parent self.months = [calendar.month_name[i] for i in range(1,13)] if value: @@ -414,41 +412,33 @@ def _enter(event) -> None: @kb.add("up") def _up(event): - if self.select_box_float not in get_app().layout.container.floats: - self.make_time(self.text) - else : + if self.select_box_float in get_app().layout.container.floats: self.select_box.up() @kb.add("down") def _down(event): - if self.select_box_float not in get_app().layout.container.floats: - self.make_time(self.text) - else : + if self.select_box_float in get_app().layout.container.floats: self.select_box.down() @kb.add("right") def _right(event): - if self.select_box_float not in get_app().layout.container.floats: - self.make_time(self.text) - else : + if self.select_box_float in get_app().layout.container.floats: self.select_box.right() - + @kb.add("left") def _left(event): - if self.select_box_float not in get_app().layout.container.floats: - self.make_time(self.text) - else : + if self.select_box_float in get_app().layout.container.floats: self.select_box.left() - @kb.add("+") - def _plus(event): - if self.select_box_float not in get_app().layout.container.floats: - self.make_time(self.text) + # @kb.add("+") + # def _plus(event): + # if self.select_box_float not in get_app().layout.container.floats: + # self.make_time(self.text) - @kb.add("-") - def _minus(event): - if self.select_box_float not in get_app().layout.container.floats: - self.make_time(self.text) + # @kb.add("-") + # def _minus(event): + # if self.select_box_float not in get_app().layout.container.floats: + # self.make_time(self.text) @kb.add("tab") def _tab(event): @@ -458,12 +448,32 @@ def _tab(event): _focus_next(event) @kb.add("escape") + def _escape(event): + if self.select_box_float in get_app().layout.container.floats: + app = get_app() + app.layout.container.floats.remove(self.select_box_float) + app.layout.focus(self.parent.dialog.navbar) + + @kb.add("pageup", eager=True) + def _pageup(event): + if self.select_box_float in get_app().layout.container.floats: + _escape(event) + self.parent.dialog.navbar.go_up() + else : + app = get_app() + self.parent.dialog.navbar.go_up() + app.layout.focus(self.parent.dialog.navbar) + @kb.add("pagedown", eager=True) - def _escape(event): + def _pagedown(event): if self.select_box_float in get_app().layout.container.floats: - get_app().layout.container.floats.remove(self.select_box_float) - _focus_next(event) + _escape(event) + self.parent.dialog.navbar.go_down() + else : + app = get_app() + self.parent.dialog.navbar.go_down() + app.layout.focus(self.parent.dialog.navbar) return kb From d2f41984f2be8e08b1c51aad43e9ec782e4aa453 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Thu, 1 Sep 2022 23:37:14 -0700 Subject: [PATCH 097/364] fix:jans_cli Change key to only remove float --- jans-cli-tui/wui_components/jans_data_picker.py | 1 - 1 file changed, 1 deletion(-) diff --git a/jans-cli-tui/wui_components/jans_data_picker.py b/jans-cli-tui/wui_components/jans_data_picker.py index db91f710292..2e5bb55adfa 100644 --- a/jans-cli-tui/wui_components/jans_data_picker.py +++ b/jans-cli-tui/wui_components/jans_data_picker.py @@ -452,7 +452,6 @@ def _escape(event): if self.select_box_float in get_app().layout.container.floats: app = get_app() app.layout.container.floats.remove(self.select_box_float) - app.layout.focus(self.parent.dialog.navbar) @kb.add("pageup", eager=True) From 4bdcd44e4586e8ced2b02dcdb828df5540e008a2 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Thu, 1 Sep 2022 23:38:59 -0700 Subject: [PATCH 098/364] fix:jans_cli add Dpcstrings --- jans-cli-tui/wui_components/jans_nav_bar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jans-cli-tui/wui_components/jans_nav_bar.py b/jans-cli-tui/wui_components/jans_nav_bar.py index cd0a512de2a..dbbfdde6246 100644 --- a/jans-cli-tui/wui_components/jans_nav_bar.py +++ b/jans-cli-tui/wui_components/jans_nav_bar.py @@ -11,7 +11,7 @@ def __init__(self, myparent, entries, selection_changed, select=0, bgcolor='#00f """init for JansNavBar Args: - myparent (_type_): _description_ + myparent (widget): This is the parent widget for the dialog, to caluclate the size entries (_type_): _description_ selection_changed (_type_): _description_ select (int, optional): _description_. Defaults to 0. From 0ab2867941e7682576753eff48b7eae5609224e9 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Thu, 1 Sep 2022 23:40:00 -0700 Subject: [PATCH 099/364] fix:jans_cli uncomment go_up and go_down --- .../wui_components/jans_side_nav_bar.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/jans-cli-tui/wui_components/jans_side_nav_bar.py b/jans-cli-tui/wui_components/jans_side_nav_bar.py index 5cc22dcbc83..eda617f7a80 100644 --- a/jans-cli-tui/wui_components/jans_side_nav_bar.py +++ b/jans-cli-tui/wui_components/jans_side_nav_bar.py @@ -92,15 +92,15 @@ def update_selection(self): self.cur_tab = self.navbar_entries[self.cur_navbar_selection] self.selection_changed(self.cur_tab) - # def go_up(self): - # self.cur_navbar_selection = ( - # self.cur_navbar_selection - 1) % len(self.navbar_entries) - # self.update_selection() - - # def go_down(self): - # self.cur_navbar_selection = ( - # self.cur_navbar_selection + 1) % len(self.navbar_entries) - # self.update_selection() + def go_up(self): + self.cur_navbar_selection = ( + self.cur_navbar_selection - 1) % len(self.navbar_entries) + self.update_selection() + + def go_down(self): + self.cur_navbar_selection = ( + self.cur_navbar_selection + 1) % len(self.navbar_entries) + self.update_selection() def get_nav_bar_key_bindings(self): """All key binding for the Dialog with Navigation bar From d1122ccfd932dbf6eca9e120e36a4fff6b192318 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Mon, 5 Sep 2022 13:16:58 +0300 Subject: [PATCH 100/364] fix: jans-cli add plugin path to sys --- jans-cli-tui/jans-cli-tui.py | 2 ++ jans-cli-tui/plugins/010_oxauth/main.py | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index 75b145ab5ca..31921041eca 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -6,6 +6,7 @@ import time import logging import importlib +import sys from pathlib import Path from asyncio import Future, ensure_future @@ -146,6 +147,7 @@ def _load_plugins(self): plugin_dir = os.path.join(cur_dir, 'plugins') for plugin_file in sorted(Path(plugin_dir).glob('*/main.py')): + sys.path.append(plugin_file.parent.as_posix()) spec = importlib.util.spec_from_file_location(plugin_file.stem, plugin_file.as_posix()) plugin = importlib.util.module_from_spec(spec) spec.loader.exec_module(plugin) diff --git a/jans-cli-tui/plugins/010_oxauth/main.py b/jans-cli-tui/plugins/010_oxauth/main.py index c073c9cce86..800ce3898c1 100644 --- a/jans-cli-tui/plugins/010_oxauth/main.py +++ b/jans-cli-tui/plugins/010_oxauth/main.py @@ -31,8 +31,6 @@ from wui_components.jans_drop_down import DropDownWidget from wui_components.jans_data_picker import DateSelectWidget -sys.path.append(os.path.dirname(os.path.realpath(__file__))) - from edit_client_dialog import EditClientDialog from edit_scope_dialog import EditScopeDialog From 285e4ca4643603b897a8512280337b6fc0a4a669 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Mon, 5 Sep 2022 15:24:37 +0300 Subject: [PATCH 101/364] fix: jans-cli dialog focus --- jans-cli-tui/jans-cli-tui.py | 7 ++----- jans-cli-tui/plugins/010_oxauth/main.py | 1 - 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index 31921041eca..1634e91632d 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -210,7 +210,6 @@ def create_cli(self): async def coroutine(): app = get_app() focused_before = app.layout.current_window - self.layout.focus(dialog) await self.show_dialog_as_float(dialog) try: app.layout.focus(focused_before) @@ -246,7 +245,6 @@ def jans_creds_dialog(self, *params): async def coroutine(): app = get_app() focused_before = app.layout.current_window - self.layout.focus(dialog) result = await self.show_dialog_as_float(dialog) try: app.layout.focus(focused_before) @@ -462,7 +460,7 @@ async def show_dialog_as_float(self, dialog): "Coroutine." float_ = Float(content=dialog) self.root_layout.floats.append(float_) - + self.layout.focus(dialog) result = await dialog.future if float_ in self.root_layout.floats: @@ -479,7 +477,6 @@ def show_jans_dialog(self, dialog): async def coroutine(): focused_before = self.layout.current_window - self.layout.focus(dialog) result = await self.show_dialog_as_float(dialog) try: self.layout.focus(focused_before) @@ -529,7 +526,7 @@ def show_message(self, title, message, buttons=[]): body = HSplit([Label(message)]) dialog = JansMessageDialog(title=title, body=body, buttons=buttons) - focused_before = self.layout.current_window + focused_before = self.root_layout.floats[-1].content if self.root_layout.floats else self.layout.current_window float_ = Float(content=dialog) self.root_layout.floats.append(float_) dialog.me = float_ diff --git a/jans-cli-tui/plugins/010_oxauth/main.py b/jans-cli-tui/plugins/010_oxauth/main.py index 800ce3898c1..fab5cae2278 100644 --- a/jans-cli-tui/plugins/010_oxauth/main.py +++ b/jans-cli-tui/plugins/010_oxauth/main.py @@ -291,7 +291,6 @@ def delete_client(self, selected, event): async def coroutine(): focused_before = self.app.layout.current_window - self.app.layout.focus(dialog) result = await self.app.show_dialog_as_float(dialog) try: self.app.layout.focus(focused_before) From 0f7366811e820c9ee36c626717535d38f352e04c Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Mon, 5 Sep 2022 15:34:05 +0300 Subject: [PATCH 102/364] fix: jans-cli avoid printing when delete --- jans-cli-tui/cli/config_cli.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jans-cli-tui/cli/config_cli.py b/jans-cli-tui/cli/config_cli.py index 7b152cf6697..49624217e95 100755 --- a/jans-cli-tui/cli/config_cli.py +++ b/jans-cli-tui/cli/config_cli.py @@ -1287,6 +1287,9 @@ def process_command_patch(self, path, suffix_param, endpoint_params, data_fn, da def process_command_delete(self, path, suffix_param, endpoint_params, data_fn, data=None): endpoint = self.get_fake_endpoint(path) response = self.delete_requests(endpoint, suffix_param) + if self.wrapped: + return response + if response: self.print_response(response) else: From b6af80c0021dfeba14086f415f6586621f7891f6 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Tue, 6 Sep 2022 05:57:12 -0700 Subject: [PATCH 103/364] feat:jans-cli add multi_lang support --- jans-cli-tui/cli/jca.yaml | 0 jans-cli-tui/cli/swagger_yaml.json | 0 jans-cli-tui/cli_style.py | 0 jans-cli-tui/docs/docs/Gallery/cli.md | 1 + jans-cli-tui/docs/docs/Gallery/gallery.md | 5 + jans-cli-tui/docs/docs/Gallery/tui.md | 5 +- jans-cli-tui/docs/docs/home/about.md | 3 +- jans-cli-tui/docs/docs/home/index.md | 150 +++++++++++++++- .../docs/models/oauth/edit_client_dialog.md | 0 .../docs/models/oauth/edit_scope_dialog.md | 0 jans-cli-tui/docs/docs/models/oauth/oauth.md | 0 jans-cli-tui/docs/docs/stylesheets/extra.css | 5 + .../docs/wui_components/jans_cli_dialog.md | 5 +- .../docs/wui_components/jans_data_picker.md | 1 + .../docs/docs/wui_components/jans_dialog.md | 1 + .../wui_components/jans_dialog_with_nav.md | 1 + .../docs/wui_components/jans_drop_down.md | 1 + .../wui_components/jans_message_dialog.md | 1 + .../docs/docs/wui_components/jans_nav_bar.md | 1 + .../docs/wui_components/jans_side_nav_bar.md | 1 + .../docs/wui_components/jans_vetrical_nav.md | 1 + .../docs/wui_components/wui_components.md | 43 +++++ jans-cli-tui/docs/img/favicon.ico | Bin jans-cli-tui/docs/img/logo.png | Bin 0 -> 4373 bytes jans-cli-tui/docs/img/new_tui/new_tui1.png | Bin jans-cli-tui/jans-cli-tui.py | 56 +++--- jans-cli-tui/mkdocs.yml | 87 +++++---- jans-cli-tui/multi_lang.py | 15 ++ .../plugins/010_oxauth/edit_client_dialog.py | 165 +++++++++--------- .../plugins/010_oxauth/edit_scope_dialog.py | 10 +- jans-cli-tui/plugins/010_oxauth/main.py | 98 ++++++++--- jans-cli-tui/plugins/020_fido/main.py | 9 + jans-cli-tui/plugins/030_scim/main.py | 9 + jans-cli-tui/plugins/040_config_api/main.py | 9 + jans-cli-tui/plugins/050_client_api/main.py | 9 + jans-cli-tui/plugins/060_scripts/main.py | 9 + jans-cli-tui/preview.ipynb | 0 jans-cli-tui/static.py | 0 jans-cli-tui/utils.py | 0 jans-cli-tui/wrapper_test.py | 0 .../wui_components/jans_cli_dialog.py | 5 +- .../wui_components/jans_data_picker.py | 1 + jans-cli-tui/wui_components/jans_dialog.py | 0 .../wui_components/jans_dialog_with_nav.py | 6 +- jans-cli-tui/wui_components/jans_drop_down.py | 11 +- .../wui_components/jans_message_dialog.py | 3 +- jans-cli-tui/wui_components/jans_nav_bar.py | 4 +- .../wui_components/jans_side_nav_bar.py | 12 +- .../wui_components/jans_vetrical_nav.py | 20 +-- 49 files changed, 544 insertions(+), 219 deletions(-) mode change 100644 => 100755 jans-cli-tui/cli/jca.yaml mode change 100644 => 100755 jans-cli-tui/cli/swagger_yaml.json mode change 100644 => 100755 jans-cli-tui/cli_style.py mode change 100644 => 100755 jans-cli-tui/docs/docs/Gallery/cli.md create mode 100755 jans-cli-tui/docs/docs/Gallery/gallery.md mode change 100644 => 100755 jans-cli-tui/docs/docs/Gallery/tui.md mode change 100644 => 100755 jans-cli-tui/docs/docs/home/about.md mode change 100644 => 100755 jans-cli-tui/docs/docs/home/index.md mode change 100644 => 100755 jans-cli-tui/docs/docs/models/oauth/edit_client_dialog.md mode change 100644 => 100755 jans-cli-tui/docs/docs/models/oauth/edit_scope_dialog.md mode change 100644 => 100755 jans-cli-tui/docs/docs/models/oauth/oauth.md create mode 100755 jans-cli-tui/docs/docs/stylesheets/extra.css mode change 100644 => 100755 jans-cli-tui/docs/docs/wui_components/jans_cli_dialog.md mode change 100644 => 100755 jans-cli-tui/docs/docs/wui_components/jans_data_picker.md mode change 100644 => 100755 jans-cli-tui/docs/docs/wui_components/jans_dialog.md mode change 100644 => 100755 jans-cli-tui/docs/docs/wui_components/jans_dialog_with_nav.md mode change 100644 => 100755 jans-cli-tui/docs/docs/wui_components/jans_drop_down.md mode change 100644 => 100755 jans-cli-tui/docs/docs/wui_components/jans_message_dialog.md mode change 100644 => 100755 jans-cli-tui/docs/docs/wui_components/jans_nav_bar.md mode change 100644 => 100755 jans-cli-tui/docs/docs/wui_components/jans_side_nav_bar.md mode change 100644 => 100755 jans-cli-tui/docs/docs/wui_components/jans_vetrical_nav.md create mode 100755 jans-cli-tui/docs/docs/wui_components/wui_components.md mode change 100644 => 100755 jans-cli-tui/docs/img/favicon.ico create mode 100755 jans-cli-tui/docs/img/logo.png mode change 100644 => 100755 jans-cli-tui/docs/img/new_tui/new_tui1.png mode change 100644 => 100755 jans-cli-tui/mkdocs.yml create mode 100755 jans-cli-tui/multi_lang.py mode change 100644 => 100755 jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py mode change 100644 => 100755 jans-cli-tui/plugins/010_oxauth/edit_scope_dialog.py mode change 100644 => 100755 jans-cli-tui/plugins/010_oxauth/main.py mode change 100644 => 100755 jans-cli-tui/plugins/020_fido/main.py mode change 100644 => 100755 jans-cli-tui/plugins/030_scim/main.py mode change 100644 => 100755 jans-cli-tui/plugins/040_config_api/main.py mode change 100644 => 100755 jans-cli-tui/plugins/050_client_api/main.py mode change 100644 => 100755 jans-cli-tui/plugins/060_scripts/main.py mode change 100644 => 100755 jans-cli-tui/preview.ipynb mode change 100644 => 100755 jans-cli-tui/static.py mode change 100644 => 100755 jans-cli-tui/utils.py mode change 100644 => 100755 jans-cli-tui/wrapper_test.py mode change 100644 => 100755 jans-cli-tui/wui_components/jans_cli_dialog.py mode change 100644 => 100755 jans-cli-tui/wui_components/jans_data_picker.py mode change 100644 => 100755 jans-cli-tui/wui_components/jans_dialog.py mode change 100644 => 100755 jans-cli-tui/wui_components/jans_dialog_with_nav.py mode change 100644 => 100755 jans-cli-tui/wui_components/jans_drop_down.py mode change 100644 => 100755 jans-cli-tui/wui_components/jans_message_dialog.py mode change 100644 => 100755 jans-cli-tui/wui_components/jans_nav_bar.py mode change 100644 => 100755 jans-cli-tui/wui_components/jans_side_nav_bar.py mode change 100644 => 100755 jans-cli-tui/wui_components/jans_vetrical_nav.py diff --git a/jans-cli-tui/cli/jca.yaml b/jans-cli-tui/cli/jca.yaml old mode 100644 new mode 100755 diff --git a/jans-cli-tui/cli/swagger_yaml.json b/jans-cli-tui/cli/swagger_yaml.json old mode 100644 new mode 100755 diff --git a/jans-cli-tui/cli_style.py b/jans-cli-tui/cli_style.py old mode 100644 new mode 100755 diff --git a/jans-cli-tui/docs/docs/Gallery/cli.md b/jans-cli-tui/docs/docs/Gallery/cli.md old mode 100644 new mode 100755 index e69de29bb2d..99dffba2c8c --- a/jans-cli-tui/docs/docs/Gallery/cli.md +++ b/jans-cli-tui/docs/docs/Gallery/cli.md @@ -0,0 +1 @@ +### args should have plugin namespace diff --git a/jans-cli-tui/docs/docs/Gallery/gallery.md b/jans-cli-tui/docs/docs/Gallery/gallery.md new file mode 100755 index 00000000000..2e8da1886e6 --- /dev/null +++ b/jans-cli-tui/docs/docs/Gallery/gallery.md @@ -0,0 +1,5 @@ +# Gallarey +gallery for all possibilities and future improvment for TUI + + +![Screenshot](docs/img/new_tui/new_tui1.PNG) \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/Gallery/tui.md b/jans-cli-tui/docs/docs/Gallery/tui.md old mode 100644 new mode 100755 index 9316bbcbd4d..349cd0f2cc0 --- a/jans-cli-tui/docs/docs/Gallery/tui.md +++ b/jans-cli-tui/docs/docs/Gallery/tui.md @@ -1,4 +1 @@ -## Gallarey -demonstrating the possibilities of jans-tui. - -![Screenshot](docs/img/new_tui/new_tui1.PNG) \ No newline at end of file +### demonstrating the possibilities of jans-tui diff --git a/jans-cli-tui/docs/docs/home/about.md b/jans-cli-tui/docs/docs/home/about.md old mode 100644 new mode 100755 index 0e87f807786..6f97087ab3b --- a/jans-cli-tui/docs/docs/home/about.md +++ b/jans-cli-tui/docs/docs/home/about.md @@ -1 +1,2 @@ ---------- \ No newline at end of file +### About TUI +# About description \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/home/index.md b/jans-cli-tui/docs/docs/home/index.md old mode 100644 new mode 100755 index 09d1f529cbc..cb6b00ec5bd --- a/jans-cli-tui/docs/docs/home/index.md +++ b/jans-cli-tui/docs/docs/home/index.md @@ -1,4 +1,150 @@ - +

Janssen Project - cloud native identity and access management platform

+ ## Welcome to the Janssen Project -The world's leading community governed digital identity platform and introducing Agama, a programming language for cloud authentication. + +Janssen enables organizations to build a scalable centralized authentication and authorization service using free open source software. The components of the project include client and server implementations of the OAuth, OpenID Connect, SCIM and FIDO standards. + +**Releases**: [Latest](https://github.com/JanssenProject/jans/releases/latest) | [All](https://github.com/JanssenProject/jans/releases) + +**Get Help**: [Discussions](https://github.com/JanssenProject/jans/discussions) | [Chat](https://gitter.im/JanssenProject/Lobby) + +**Get Started**: [Quick Start](#quick-start) | [User Guides](docs/user) + +**Contribute**: [Contribution Guide](docs/CONTRIBUTING.md) | [Community Docs](docs/community) | [Developer Guides](docs/developer) + +[![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/janssen-auth-server)](https://artifacthub.io/packages/search?repo=janssen-auth-server) +[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4353/badge)](https://bestpractices.coreinfrastructure.org/projects/4353) +![Hex.pm](https://img.shields.io/hexpm/l/plug) +![GitHub contributors](https://img.shields.io/github/contributors/janssenproject/jans) +[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196?logo=conventionalcommits&logoColor=white)](https://conventionalcommits.org) + +**Table of Contents** + + +- [Janssen Modules](#janssen-modules) +- [Getting Started](#getting-started) + - [Installation](#installation) +- [Users and Community](#users-and-community) +- [Contributing](#contributing) + - [Code of Conduct](#code-of-conduct) + - [Contribution Guidelines](#contribution-guidelines) +- [Security](#security) +- [Documentation](#documentation) +- [Design](#design) + - [Design Goals](#design-goals) +- [Governance](#governance) +- [Support](#support) +- [More about Janssen Project](#more-about-janssen-project) + - [History](#history) + - [why the name Janssen](#why-the-name-janssen) + + + +## Janssen Modules + +Janssen is not a big monolith--it's a lot of services working together. Whether you deploy Janssen to a Kubernetes cluster, or you are a developer running everything on one server, it's important to understand the different parts. + +1. **[jans-auth-server](jans-auth-server)**: This component is the OAuth Authorization Server, the OpenID Connect Provider, the UMA Authorization Server--this is the main Internet facing component of Janssen. It's the service that returns tokens, JWT's and identity assertions. This service must be Internet facing. + +1. **[jans-fido2](jans-fido2)**: This component provides the server side endpoints to enroll and validate devices that use FIDO. It provides both FIDO U2F (register, authenticate) and FIDO 2 (attestation, assertion) endpoints. This service must be internet facing. + +1. **[jans-config-api](jans-config-api)**: The API to configure the auth-server and other components is consolidated in this component. This service should not be Internet-facing. + +1. **[jans-scim](jans-scim)**: [SCIM](http://www.simplecloud.info/) is JSON/REST API to manage user data. Use it to add, edit and update user information. This service should not be Internet facing. + +1. **[jans-cli](jans-cli)**: This module is a command line interface for configuring the Janssen software, providing both interactive and simple single line + options for configuration. + +1. **[jans-client-api](jans-client-api)**: Middleware API to help application developers call an OAuth, OpenID or UMA server. You may wonder why this is necessary. It makes it easier for client developers to use OpenID signing and encryption features, without becoming crypto experts. This API provides some high level endpoints to do some of the heavy lifting. + +1. **[jans-core](jans-core)**: This library has code that is shared across several janssen projects. You will most likely need this project when you build other Janssen components. + +1. **[jans-orm](jans-orm)**: This is the library for persistence and caching implemenations in Janssen. Currently LDAP and Couchbase are supported. RDBMS is coming soon. + +1. **[Agama](agama)**: Agama module offers an alternative way to build authentication flows in Janssen Server. With Agama, flows are coded in a DSL (domain specific language) designed for the sole purpose of writing web flows. + +## Getting Started + +### Installation + +Janssen can be installed as cloud-native in a Kubernetes cluster or as a server on a single VM. Go to the [Janssen Project Wiki](https://github.com/JanssenProject/jans/wiki/) to know all the installation options + +## Users and Community + +A BIG thanks to all amazing contributors!! 👏 👏 + +There are many ways you can contribute. Of course you can contribute code. But we also need people to write documentation and guides, to help us with testing, to answer questions on the forums and chat, to review PR's, to help us with devops and CI/CD, to provide feedback on usability, and to promote the project through outreach. Also, by sharing metrics with us, we can gain valuable insights into how the software performs in the wild. + +Building a large community is our number one goal. Please let us know what we can do to make you feel more welcome, no matter what you want to contribute. + +
+ + + +## Contributing + +### Code of Conduct + +[Janssen code of conduct](docs/CODE_OF_CONDUCT.md) ensures that Janssen community is a welcoming place for everyone. + +### Contribution Guidelines + +[Contribution guide](docs/CONTRIBUTING.md) will give you all necessary information and `howto` to get started. Janssen community welcomes all types of contributions. Be it an interesting comment on an open issue or implementing a feature. Welcome aboard! ✈️ + +## Security + +### Disclosing vulnerabilities +If you think you found a security vulnerability, please refrain from posting it publicly on the forums, the chat, or GitHub. Instead, send us an email on security@jans.io. + +Refer to [Janssen Security Policy](.github/SECURITY.md) + +## Documentation + +Refer to [Janssen Wiki](https://github.com/JanssenProject/jans/wiki) for documentation. + +## Design + +### Design Goals + +The Janssen Project is aligned with the goals of cloud native infrastructure to enable: + +1. High Concurrency: For digital identity infrastructure, the number of users is not necessarily related to performance. If you have a billion users who never login, you can do this with a monolithic platform. Concurrency is hard. Janssen is designed to scale horizontally--enabling hypothetically any concurrency by adding more compute and memory. + +2. Highly Available: Digital identity infrastructure is mission critical. For many applications, if you can't login, you're dead in the water. Robustness is a fundamental consideration. + +3. Flexible while Upgradable: Open source gives you the freedom to modify the code. But having your own fork of the code might make it hard to upgrade--you'll have to merge changes. Janssen provides standard interfaces that make it possible to implement custom business logic in an upgrade-friendly manner. + +## Governance + +Janssen is a Linux Foundation project, governed according to the [charter](./docs/community/charter.md). Technical oversight of the project is the responsibility of the Technical Steering Committee ("TSC"). Day to day decision making is in the hands of the Contributors. The TSC helps to guide the direction of the project and to improve the quality and security of the development process. + +## Support + +Documentation currently is a work in progress. Draft pages are currently available on [Janssen Project Wiki](https://github.com/JanssenProject/jans/wiki/). You may want to also check Gluu Server [docs](https://gluu.org/docs), which have a lot in common with Janssen. + +We prefer to have all our discussions through [GitHub Discussions](https://github.com/JanssenProject/jans/discussion) to better facilitate faster responses. However, other means are available such as the [community chat on Gitter](https://gitter.im/JanssenProject/Lobby). You can register for free there with your Github identity. + +If you find a bug in a Janssen project, or you would like to suggest a new feature, try the [GitHub Discussions](https://github.com/JanssenProject/jans/discussion) first. If you have a "howto" or "usage" question, [raise the question or usage](https://github.com/JanssenProject/jans/discussion)! + +## Releases + +Below are the list of current mega releases that hold information about each single release of our servies and modules: + +- [v1.0.2](https://github.com/JanssenProject/jans/releases/tag/v1.0.2) +- [v1.0.1](https://github.com/JanssenProject/jans/releases/tag/v1.0.1) +- [v1.0.0](https://github.com/JanssenProject/jans/releases/tag/v1.0.0) +- [v1.0.0-beta.16](https://github.com/JanssenProject/jans/releases/tag/v1.0.0-beta.16) +- [v1.0.0-beta.15](https://github.com/JanssenProject/jans/releases/tag/v1.0.0-beta.15) + +## More about Janssen Project + +### History + +The initial code was ported by [Gluu](https://gluu.org), based on version 4.2 of it's identity and access management (IAM) platform. Gluu launched in 2009 with the goal of creating an enterprise-grade open source distribution of IAM components. In 2012, Gluu started work on an OAuth Authorization Server to implement OpenID Connect, which they saw as a promising next-generation replacement for SAML. This project was called [oxAuth](https://github.com/GluuFederation/oxauth), and over time, became the core component of the Gluu Server. Gluu has submitted many [self-certifications](https://openid.net/certification/) at the OpenID Foundation. Today, it is one of the most comprehensive OpenID Connect Providers. + +In 2020, Gluu decided to democratize the governance of the oxAuth project by moving it to the Linux Foundation. The name of the project was changed from oxAuth to Janssen, to avoid any potential trademark issues. Gluu felt that a collaboration with the Linux Foundation would help to build a larger ecosystem. + +### Why the name Janssen? + +Pigeons (or doves if you like...) are universally regarded as a symbol of peace. But they are also fast. Powered by a handful of seeds, a well trained racing pigeon can fly 1000 kilometers in a day. The Janssen brothers of Arendonk in Belgium bred the world's fastest family of racing pigeons. Complex open source infrastructure, like competitive animal husbandry, requires incremental improvement. Janssen racing pigeons revolutionized the sport. The Janssen Project seeks to revolutionize identity and access management. diff --git a/jans-cli-tui/docs/docs/models/oauth/edit_client_dialog.md b/jans-cli-tui/docs/docs/models/oauth/edit_client_dialog.md old mode 100644 new mode 100755 diff --git a/jans-cli-tui/docs/docs/models/oauth/edit_scope_dialog.md b/jans-cli-tui/docs/docs/models/oauth/edit_scope_dialog.md old mode 100644 new mode 100755 diff --git a/jans-cli-tui/docs/docs/models/oauth/oauth.md b/jans-cli-tui/docs/docs/models/oauth/oauth.md old mode 100644 new mode 100755 diff --git a/jans-cli-tui/docs/docs/stylesheets/extra.css b/jans-cli-tui/docs/docs/stylesheets/extra.css new file mode 100755 index 00000000000..50bed088c99 --- /dev/null +++ b/jans-cli-tui/docs/docs/stylesheets/extra.css @@ -0,0 +1,5 @@ +:root > * { + --md-primary-fg-color: #EE0F0F; + --md-primary-fg-color--light: #ECB7B7; + --md-primary-fg-color--dark: #90030C; + } \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/wui_components/jans_cli_dialog.md b/jans-cli-tui/docs/docs/wui_components/jans_cli_dialog.md old mode 100644 new mode 100755 index 14a881a8fcc..04392f123ad --- a/jans-cli-tui/docs/docs/wui_components/jans_cli_dialog.md +++ b/jans-cli-tui/docs/docs/wui_components/jans_cli_dialog.md @@ -1 +1,4 @@ -::: wui_components.jans_cli_dialog.JansGDialog \ No newline at end of file +### JansGDialog +::: wui_components.jans_cli_dialog.JansGDialog + + diff --git a/jans-cli-tui/docs/docs/wui_components/jans_data_picker.md b/jans-cli-tui/docs/docs/wui_components/jans_data_picker.md old mode 100644 new mode 100755 index 9f6ff4a0c95..0fb98b7bec5 --- a/jans-cli-tui/docs/docs/wui_components/jans_data_picker.md +++ b/jans-cli-tui/docs/docs/wui_components/jans_data_picker.md @@ -1 +1,2 @@ +### DateSelectWidget ::: wui_components.jans_data_picker.DateSelectWidget \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/wui_components/jans_dialog.md b/jans-cli-tui/docs/docs/wui_components/jans_dialog.md old mode 100644 new mode 100755 index d6c0aab6fe1..2026900a141 --- a/jans-cli-tui/docs/docs/wui_components/jans_dialog.md +++ b/jans-cli-tui/docs/docs/wui_components/jans_dialog.md @@ -1 +1,2 @@ +### JansDialog ::: wui_components.jans_dialog.JansDialog \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/wui_components/jans_dialog_with_nav.md b/jans-cli-tui/docs/docs/wui_components/jans_dialog_with_nav.md old mode 100644 new mode 100755 index 8abd40c100b..e33e4d79185 --- a/jans-cli-tui/docs/docs/wui_components/jans_dialog_with_nav.md +++ b/jans-cli-tui/docs/docs/wui_components/jans_dialog_with_nav.md @@ -1 +1,2 @@ +### JansDialogWithNav ::: wui_components.jans_dialog_with_nav.JansDialogWithNav \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/wui_components/jans_drop_down.md b/jans-cli-tui/docs/docs/wui_components/jans_drop_down.md old mode 100644 new mode 100755 index cfe591f5ba3..c22d9867542 --- a/jans-cli-tui/docs/docs/wui_components/jans_drop_down.md +++ b/jans-cli-tui/docs/docs/wui_components/jans_drop_down.md @@ -1 +1,2 @@ +### DropDownWidget ::: wui_components.jans_drop_down.DropDownWidget \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/wui_components/jans_message_dialog.md b/jans-cli-tui/docs/docs/wui_components/jans_message_dialog.md old mode 100644 new mode 100755 index 95ac255e11d..5e6d5b44d8c --- a/jans-cli-tui/docs/docs/wui_components/jans_message_dialog.md +++ b/jans-cli-tui/docs/docs/wui_components/jans_message_dialog.md @@ -1 +1,2 @@ +### JansMessageDialog ::: wui_components.jans_message_dialog.JansMessageDialog \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/wui_components/jans_nav_bar.md b/jans-cli-tui/docs/docs/wui_components/jans_nav_bar.md old mode 100644 new mode 100755 index 8fc344035ca..a5f9869af28 --- a/jans-cli-tui/docs/docs/wui_components/jans_nav_bar.md +++ b/jans-cli-tui/docs/docs/wui_components/jans_nav_bar.md @@ -1 +1,2 @@ +### JansNavBar ::: wui_components.jans_nav_bar.JansNavBar \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/wui_components/jans_side_nav_bar.md b/jans-cli-tui/docs/docs/wui_components/jans_side_nav_bar.md old mode 100644 new mode 100755 index bc6002ed7e1..f2e03ed8812 --- a/jans-cli-tui/docs/docs/wui_components/jans_side_nav_bar.md +++ b/jans-cli-tui/docs/docs/wui_components/jans_side_nav_bar.md @@ -1 +1,2 @@ +### JansSideNavBar ::: wui_components.jans_side_nav_bar.JansSideNavBar \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/wui_components/jans_vetrical_nav.md b/jans-cli-tui/docs/docs/wui_components/jans_vetrical_nav.md old mode 100644 new mode 100755 index 6258f944dd9..41701329574 --- a/jans-cli-tui/docs/docs/wui_components/jans_vetrical_nav.md +++ b/jans-cli-tui/docs/docs/wui_components/jans_vetrical_nav.md @@ -1 +1,2 @@ +### JansVerticalNav ::: wui_components.jans_vetrical_nav.JansVerticalNav \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/wui_components/wui_components.md b/jans-cli-tui/docs/docs/wui_components/wui_components.md new file mode 100755 index 00000000000..42cb9d79dc0 --- /dev/null +++ b/jans-cli-tui/docs/docs/wui_components/wui_components.md @@ -0,0 +1,43 @@ +# Components + +## Content + +This Sction contains all the wui components + +**there are diffrent types such as** +### Dialogs components + +- jans_cli_dialog +- jans_dialog_with_nav +- jans_message_dialog + +### Navigation bar components + +- jans_nav_bar +- jans_side_nav_bar +- jans_vetrical_nav + +### Custom components +- jans_data_picker +- jans_drop_down + + \ No newline at end of file diff --git a/jans-cli-tui/docs/img/favicon.ico b/jans-cli-tui/docs/img/favicon.ico old mode 100644 new mode 100755 diff --git a/jans-cli-tui/docs/img/logo.png b/jans-cli-tui/docs/img/logo.png new file mode 100755 index 0000000000000000000000000000000000000000..d925cd28c4147fef1cb64c13304e195e6b0bb07a GIT binary patch literal 4373 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 Edit current selection\n Display current item in JSON format\n Delete current selection''') + self.show_message(_("Help"),''' {} \n {}\n {}'''.format(_("Edit current selection"),_("Display current item in JSON format"),_("Delete current selection"))) # ----------------------------------------------------------------- # def get_help_from_schema(self, schema, jans_name): @@ -451,13 +449,13 @@ def get_plugin_by_id(self, pid): def main_nav_selection_changed(self, selection): - self.logger.debug("Main navbar selection changed %s", str(selection)) + self.logger.debug('Main navbar selection changed %s', str(selection)) plugin = self.get_plugin_by_id(selection) plugin.set_center_frame() async def show_dialog_as_float(self, dialog): - "Coroutine." + 'Coroutine.' float_ = Float(content=dialog) self.root_layout.floats.append(float_) self.layout.focus(dialog) @@ -491,7 +489,7 @@ def data_display_dialog(self, **params): body = HSplit([ TextArea( - lexer=DynamicLexer(lambda: PygmentsLexer.from_filename(".json", sync_from_start=True)), + lexer=DynamicLexer(lambda: PygmentsLexer.from_filename('.json', sync_from_start=True)), scrollbar=True, line_numbers=True, multiline=True, @@ -535,13 +533,13 @@ def show_message(self, title, message, buttons=[]): self.press_tab() def show_again(self): ## nasted dialog Button - self.show_message("Again", "Nasted Dialogs",) + self.show_message(_("Again"), _("Nasted Dialogs"),) def get_confirm_dialog(self, message): body = VSplit([Label(message)], align=HorizontalAlign.CENTER) - buttons = [Button("No"), Button("Yes")] - dialog = JansGDialog(self, title="Confirmation", body=body, buttons=buttons) + buttons = [Button(_("No")), Button(_("Yes"))] + dialog = JansGDialog(self, title=_("Confirmation"), body=body, buttons=buttons) return dialog diff --git a/jans-cli-tui/mkdocs.yml b/jans-cli-tui/mkdocs.yml old mode 100644 new mode 100755 index 0517cb649d3..7a0fe96319f --- a/jans-cli-tui/mkdocs.yml +++ b/jans-cli-tui/mkdocs.yml @@ -3,53 +3,64 @@ site_url: 'https://gluu.org/gluu-4/' repo_url: 'https://github.com/JanssenProject' edit_uri: '#https://github.com/JanssenProject' site_description: "this is desc" + plugins: - mkdocstrings + # - search + nav: - Home: 'docs/home/index.md' - - Gallery: - - 'CLI': 'docs/Gallery/cli.md' - - 'TUI': 'docs/Gallery/tui.md' - - Components: + - Gallery: + - 'docs/Gallery/gallery.md' + - TUI : + - 'docs/Gallery/tui.md' + - CLI : + - 'docs/Gallery/cli.md' + + - Components: + - docs/wui_components/wui_components.md + - Dialogs components : - 'jans_cli_dialog': 'docs/wui_components/jans_cli_dialog.md' - - 'jans_data_picker': 'docs/wui_components/jans_data_picker.md' - 'jans_dialog_with_nav': 'docs/wui_components/jans_dialog_with_nav.md' - - 'jans_drop_down': 'docs/wui_components/jans_drop_down.md' - 'jans_message_dialog': 'docs/wui_components/jans_message_dialog.md' + - Navigation bar components : - 'jans_nav_bar': 'docs/wui_components/jans_nav_bar.md' - 'jans_side_nav_bar': 'docs/wui_components/jans_side_nav_bar.md' - 'jans_vetrical_nav': 'docs/wui_components/jans_vetrical_nav.md' + - Custom components : + - 'jans_data_picker': 'docs/wui_components/jans_data_picker.md' + - 'jans_drop_down': 'docs/wui_components/jans_drop_down.md' - - TUI Main Components: - - 'Clinets': 'docs/Clinets/Clinets.md' - - 'Scopes': 'docs/Scopes/Scopes.md' - - 'Keys': 'docs/Keys/Keys.md' - - 'Defaults': 'docs/Defaults/Defaults.md' - - 'Properties': 'docs/Properties/Properties.md' - - 'Logging': 'docs/Logging/Logging.md' - - Clinets: - - 'Add Credintials': 'docs/Clinets/AddCredintials.md' - - 'Get Device Verification': 'docs/Clinets/Get.md' - - 'Add New Client': 'docs/Clinets/Add.md' - - 'Remove Client': 'docs/Clinets/Remove.md' - - 'Edit Client': 'docs/Clinets/Edit.md' - - 'Search Client': 'docs/Clinets/Search.md' - - Scopes: - - 'Scopes': 'docs/Scopes/Scopes.md' - - Keys: - - 'Keys': 'docs/Keys/Keys.md' - - Defaults: - - 'Defaults': 'docs/Defaults/Defaults.md' - - Properties: - - 'Properties': 'docs/Properties/Properties.md' - - Logging: - - 'Logging': 'docs/Logging/Logging.md' - - About: - - 'License': 'license.md' - - 'Release Notes': 'release-notes.md' theme: - name: readthedocs - locale: en - include_sidebar: false -# theme: -# readthedocs + homepage: https://example.com + name: material + + + logo: img/logo.png + favicon: img/favicon.ico + + features: + - navigation.instant + - toc.follow + - navigation.tabs + - navigation.tabs.sticky + - navigation.top + + + palette: + - media: "(prefers-color-scheme: light)" + scheme: default + primary: white + accent: green + toggle: + icon: material/weather-sunny + name: Switch to dark mode + + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: black + accent: green + toggle: + icon: material/weather-night + name: Switch to light mode + diff --git a/jans-cli-tui/multi_lang.py b/jans-cli-tui/multi_lang.py new file mode 100755 index 00000000000..7b67282f592 --- /dev/null +++ b/jans-cli-tui/multi_lang.py @@ -0,0 +1,15 @@ +import locale +import gettext + +language = 'en' + +try: + current_locale, encoding = locale.getdefaultlocale() + language = gettext.translation (language, 'locale/', languages=[language] ) + language.install() +except: + pass + +_ = gettext.gettext + + diff --git a/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py b/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py old mode 100644 new mode 100755 index 47dfe4a0944..a7e7cc7c496 --- a/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py +++ b/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py @@ -22,6 +22,9 @@ from wui_components.jans_data_picker import DateSelectWidget from utils import DialogUtils +from multi_lang import _ + + class EditClientDialog(JansGDialog, DialogUtils): """The Main Client Dialog that contain every thing related to The Client """ @@ -32,11 +35,11 @@ def __init__(self, parent, title, data, buttons=[], save_handler=None): DialogUtils (methods): Responsable for all `make data from dialog` and `check required fields` in the form for any Edit or Add New Args: - parent (widget): _description_ - title (_type_): _description_ - data (_type_): _description_ - buttons (list, optional): _description_. Defaults to []. - save_handler (_type_, optional): _description_. Defaults to None. + parent (widget): This is the parent widget for the dialog, to access `Pageup` and `Pagedown` + title (str): The Main dialog title + data (list): selected line data + button_functions (list, optional): Dialog main buttons with their handlers. Defaults to []. + save_handler (method, optional): _description_. Defaults to None. """ super().__init__(parent, title, buttons) self.save_handler = save_handler @@ -66,11 +69,11 @@ def save(): self.data['rptAsJwt'] = self.data['rptAsJwt'] == 'jwt' cfr = self.check_required_fields() - self.myparent.logger.debug("CFR: "+str(cfr)) + self.myparent.logger.debug('CFR: '+str(cfr)) if not cfr: return - self.myparent.logger.debug("handler: "+str(save_handler)) + self.myparent.logger.debug('handler: '+str(save_handler)) close_me = True if save_handler: close_me = self.save_handler(self) @@ -91,8 +94,8 @@ def cancel(): navbar=self.side_nav_bar, content=DynamicContainer(lambda: self.tabs[self.left_nav]), button_functions=[ - (save, "Save"), - (cancel, "Cancel") + (save, _("Save")), + (cancel, _("Cancel")) ], height=self.myparent.dialog_height, width=self.myparent.dialog_width, @@ -100,7 +103,8 @@ def cancel(): def prepare_tabs(self): - + """Prepare the tabs for Edil Client Dialogs + """ schema = self.myparent.cli_object.get_schema_from_reference('#/components/schemas/Client') @@ -115,12 +119,12 @@ def prepare_tabs(self): read_only=True, style='green'), - self.myparent.getTitledCheckBox("Active", name='disabled', checked= not self.data.get('disabled'), jans_help=self.myparent.get_help_from_schema(schema, 'disabled'), style='green'), - self.myparent.getTitledText("Client Name", name='displayName', value=self.data.get('displayName',''), jans_help=self.myparent.get_help_from_schema(schema, 'displayName'), style='green'), - self.myparent.getTitledText("Client Secret", name='clientSecret', value=self.data.get('clientSecret',''), jans_help=self.myparent.get_help_from_schema(schema, 'clientSecret'), style='green'), - self.myparent.getTitledText("Description", name='description', value=self.data.get('description',''), style='green'), + self.myparent.getTitledCheckBox(_("Active"), name='disabled', checked= not self.data.get('disabled'), jans_help=self.myparent.get_help_from_schema(schema, 'disabled'), style='green'), + self.myparent.getTitledText(_("Client Name"), name='displayName', value=self.data.get('displayName',''), jans_help=self.myparent.get_help_from_schema(schema, 'displayName'), style='green'), + self.myparent.getTitledText(_("Client Secret"), name='clientSecret', value=self.data.get('clientSecret',''), jans_help=self.myparent.get_help_from_schema(schema, 'clientSecret'), style='green'), + self.myparent.getTitledText(_("Description"), name='description', value=self.data.get('description',''), style='green'), self.myparent.getTitledWidget( - "Authn Method token endpoint", + _("Authn Method token endpoint"), name='tokenEndpointAuthMethodsSupported', widget=DropDownWidget( values=[('client_secret_basic', 'client_secret_basic'), ('client_secret_post', 'client_secret_post'), ('client_secret_jwt', 'client_secret_jwt'), ('private_key_jwt', 'private_key_jwt')], @@ -130,34 +134,34 @@ def prepare_tabs(self): style='green' ), self.myparent.getTitledRadioButton( - "Subject Type", + _("Subject Type"), name='subjectType', values=[('public', 'Public'),('pairwise', 'Pairwise')], current_value=self.data.get('subjectType'), jans_help=self.myparent.get_help_from_schema(schema, 'subjectType'), style='green'), self.myparent.getTitledCheckBoxList( - "Grant", + _("Grant"), name='grantTypes', values=[('authorization_code', 'Authorization Code'), ('refresh_token', 'Refresh Token'), ('urn:ietf:params:oauth:grant-type:uma-ticket', 'UMA Ticket'), ('client_credentials', 'Client Credentials'), ('password', 'Password'), ('implicit', 'Implicit')], current_values=self.data.get('grantTypes', []), jans_help=self.myparent.get_help_from_schema(schema, 'grantTypes'), style='green'), self.myparent.getTitledCheckBoxList( - "Response Types", + _("Response Types"), name='responseTypes', values=['code', 'token', 'id_token'], current_values=self.data.get('responseTypes', []), jans_help=self.myparent.get_help_from_schema(schema, 'responseTypes'), style='green'), - self.myparent.getTitledCheckBox("Supress Authorization", + self.myparent.getTitledCheckBox(_("Supress Authorization"), name='dynamicRegistrationPersistClientAuthorizations', checked=self.data.get('dynamicRegistrationPersistClientAuthorizations'), style='green'), - self.myparent.getTitledRadioButton("Application Type", name='applicationType', values=['native','web'], current_value=self.data.get('applicationType'), style='green'), - self.myparent.getTitledText("Redirect Uris", name='redirectUris', value='\n'.join(self.data.get('redirectUris', [])), height=3, style='class:required-field'), - self.myparent.getTitledText("Redirect Regex", name='redirectUrisRegex', value=self.data.get('redirectUrisRegex', ''), style='green'), - self.myparent.getTitledText("Scopes", + self.myparent.getTitledRadioButton(_("Application Type"), name='applicationType', values=['native','web'], current_value=self.data.get('applicationType'), style='green'), + self.myparent.getTitledText(_("Redirect Uris"), name='redirectUris', value='\n'.join(self.data.get('redirectUris', [])), height=3, style='class:required-field'), + self.myparent.getTitledText(_("Redirect Regex"), name='redirectUrisRegex', value=self.data.get('redirectUrisRegex', ''), style='green'), + self.myparent.getTitledText(_("Scopes"), name='scopes', value='\n'.join(self.data.get('scopes', [])), height=3, @@ -168,7 +172,7 @@ def prepare_tabs(self): self.tabs['Tokens'] = HSplit([ self.myparent.getTitledRadioButton( - "Access Token Type", + _("Access Token Type"), name='accessTokenAsJwt', values=[('jwt', 'JWT'), ('reference', 'Reference')], current_value= 'jwt' if self.data.get('accessTokenAsJwt') else 'reference', @@ -176,41 +180,41 @@ def prepare_tabs(self): style='green'), self.myparent.getTitledCheckBox( - "Incliude Claims in id_token", + _("Incliude Claims in id_token"), name='includeClaimsInIdToken', checked=self.data.get('includeClaimsInIdToken'), style='green'), self.myparent.getTitledCheckBox( - "Run introspection script before JWT access token creation", + _("Run introspection script before JWT access token creation"), name='runIntrospectionScriptBeforeAccessTokenAsJwtCreationAndIncludeClaims', checked=self.data.get('runIntrospectionScriptBeforeAccessTokenAsJwtCreationAndIncludeClaims'), style='green'), self.myparent.getTitledText( - title="Token binding confirmation method for id_token", + title=_("Token binding confirmation method for id_token"), name='idTokenTokenBindingCnf', value=self.data.get('idTokenTokenBindingCnf',''), style='green'), self.myparent.getTitledText( - title="Access token additional audiences", + title=_("Access token additional audiences"), name='additionalAudience', value=self.data.get('additionalAudience',''), style='green'), VSplit([ Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3) ]), - self.myparent.getTitledText("Access token lifetime", name='accessTokenLifetime', value=self.data.get('accessTokenLifetime',''),style='green'), - self.myparent.getTitledText("Refresh token lifetime", name='refreshTokenLifetime', value=self.data.get('refreshTokenLifetime',''),style='green'), - self.myparent.getTitledText("Defult max authn age", name='defaultMaxAge', value=self.data.get('defaultMaxAge',''),style='green'), + self.myparent.getTitledText(_("Access token lifetime"), name='accessTokenLifetime', value=self.data.get('accessTokenLifetime',''),style='green'), + self.myparent.getTitledText(_("Refresh token lifetime"), name='refreshTokenLifetime', value=self.data.get('refreshTokenLifetime',''),style='green'), + self.myparent.getTitledText(_("Defult max authn age"), name='defaultMaxAge', value=self.data.get('defaultMaxAge',''),style='green'), ],width=D()) self.tabs['Logout'] = HSplit([ - self.myparent.getTitledText("Front channel logout URI", name='frontChannelLogoutUri', value=self.data.get('frontChannelLogoutUri',''), style='green'), - self.myparent.getTitledText("Post logout redirect URIs", name='postLogoutRedirectUris', value='\n'.join(self.data.get('postLogoutRedirectUris',[])), height=3, style='green'), - self.myparent.getTitledText("Back channel logout URI", name='backchannelLogoutUri', value=self.data.get('backchannelLogoutUri',''),style='green'), - self.myparent.getTitledCheckBox("Back channel logout session required", name='backchannelLogoutSessionRequired', checked=self.data.get('backchannelLogoutSessionRequired'),style='green'), - self.myparent.getTitledCheckBox("Front channel logout session required", name='frontChannelLogoutSessionRequired', checked=self.data.get('frontChannelLogoutSessionRequired'),style='green'), + self.myparent.getTitledText(_("Front channel logout URI"), name='frontChannelLogoutUri', value=self.data.get('frontChannelLogoutUri',''), style='green'), + self.myparent.getTitledText(_("Post logout redirect URIs"), name='postLogoutRedirectUris', value='\n'.join(self.data.get('postLogoutRedirectUris',[])), height=3, style='green'), + self.myparent.getTitledText(_("Back channel logout URI"), name='backchannelLogoutUri', value=self.data.get('backchannelLogoutUri',''),style='green'), + self.myparent.getTitledCheckBox(_("Back channel logout session required"), name='backchannelLogoutSessionRequired', checked=self.data.get('backchannelLogoutSessionRequired'),style='green'), + self.myparent.getTitledCheckBox(_("Front channel logout session required"), name='frontChannelLogoutSessionRequired', checked=self.data.get('frontChannelLogoutSessionRequired'),style='green'), ],width=D() ) @@ -221,85 +225,85 @@ def prepare_tabs(self): #self.myparent.getTitledText(title ="Logo URI", name='logoUri', value=self.data.get('logoUri',''),style='green'), #self.myparent.getTitledText(title ="Term of service URI", name='tosUri', value=self.data.get('tosUri',''),style='green'), - self.myparent.getTitledText("Contacts", + self.myparent.getTitledText(_("Contacts"), name='contacts', value='\n'.join(self.data.get('contacts', [])), height=3, style='green'), - self.myparent.getTitledText("Authorized JS origins", + self.myparent.getTitledText(_("Authorized JS origins"), name='authorizedOrigins', value='\n'.join(self.data.get('authorizedOrigins', [])), height=3, style='green'), - self.myparent.getTitledText(title ="Software id", name='softwareId', value=self.data.get('softwareId',''),style='green'), - self.myparent.getTitledText(title ="Software version", name='softwareVersion', value=self.data.get('softwareVersion',''), style='green'), - self.myparent.getTitledText(title ="Software statement", name='softwareStatement', value=self.data.get('softwareStatement',''), style='green'), + self.myparent.getTitledText(title =_("Software id"), name='softwareId', value=self.data.get('softwareId',''),style='green'), + self.myparent.getTitledText(title =_("Software version"), name='softwareVersion', value=self.data.get('softwareVersion',''), style='green'), + self.myparent.getTitledText(title =_("Software statement"), name='softwareStatement', value=self.data.get('softwareStatement',''), style='green'), ],width=D()) self.tabs['CIBA/PAR/UMA'] = HSplit([ - Label(text="CIBA",style='bold'), - self.myparent.getTitledRadioButton("Token delivery method", name='backchannelTokenDeliveryMode', current_value=self.data.get('backchannelTokenDeliveryMode'), values=['poll','push', 'ping'],style='green'), - self.myparent.getTitledText(title ="Client notification endpoint", name='backchannelClientNotificationEndpoint', value=self.data.get('backchannelClientNotificationEndpoint',''),style='green'), - self.myparent.getTitledCheckBox("Require user code param", name='backchannelUserCodeParameterSupported', checked=self.data.get('backchannelUserCodeParameterSupported'),style='green'), + Label(text=_("CIBA"),style='bold'), + self.myparent.getTitledRadioButton(_("Token delivery method"), name='backchannelTokenDeliveryMode', current_value=self.data.get('backchannelTokenDeliveryMode'), values=['poll','push', 'ping'],style='green'), + self.myparent.getTitledText(title =_("Client notification endpoint"), name='backchannelClientNotificationEndpoint', value=self.data.get('backchannelClientNotificationEndpoint',''),style='green'), + self.myparent.getTitledCheckBox(_("Require user code param"), name='backchannelUserCodeParameterSupported', checked=self.data.get('backchannelUserCodeParameterSupported'),style='green'), - Label(text="PAR",style='bold'), + Label(text=_("PAR"),style='bold'), - self.myparent.getTitledText(title ="Request lifetime", name='parLifetime', value=self.data.get('parLifetime',''),style='green'), - self.myparent.getTitledCheckBox("Request PAR", name='sessionIdRequestParameterEnabled', checked=self.data.get('sessionIdRequestParameterEnabled'),style='green'), + self.myparent.getTitledText(title =_("Request lifetime"), name='parLifetime', value=self.data.get('parLifetime',''),style='green'), + self.myparent.getTitledCheckBox(_("Request PAR"), name='sessionIdRequestParameterEnabled',checked=self.data.get('sessionIdRequestParameterEnabled'),style='green'), - Label("UMA", style='bold'), + Label(_("UMA"), style='bold'), self.myparent.getTitledRadioButton( - "PRT token type", + _("PRT token type"), name='rptAsJwt!', values=[('jwt', 'JWT'), ('reference', 'Reference')], current_value='jwt' if self.data.get('rptAsJwt') else 'reference', style='green'), - self.myparent.getTitledText(title ="Claims redirect URI", name='claimRedirectUris', value=self.data.get('claimRedirectUris',''),style='green'), + self.myparent.getTitledText(title =_("Claims redirect URI"), name='claimRedirectUris', value=self.data.get('claimRedirectUris',''),style='green'), - self.myparent.getTitledText("RPT Mofification Script", + self.myparent.getTitledText(_("RPT Mofification Script"), name='rptClaimsScripts', value='\n'.join(self.data.get('rptClaimsScripts', [])), height=3, style='green'), - self.myparent.getTitledText("Claims Gathering Script", + self.myparent.getTitledText(_("Claims Gathering Script"), name='claimRedirectUris', value='\n'.join(self.data.get('claimRedirectUris', [])), height=3, style='green'), - Label(text="tabel",style='blue'), ## TODO with Jans VerticalNav + Label(text=_("tabel"),style='blue'), ## TODO with Jans VerticalNav ] ) self.tabs['Encryption/Signing'] = HSplit([ - self.myparent.getTitledText(title ="Client JWKS URI", name='jwksUri', value=self.data.get('jwksUri',''),style='green'), - self.myparent.getTitledText(title ="Client JWKS", name='jwks', value=self.data.get('jwks',''),style='green'), + self.myparent.getTitledText(title =_("Client JWKS URI"), name='jwksUri', value=self.data.get('jwksUri',''),style='green'), + self.myparent.getTitledText(title =_("Client JWKS"), name='jwks', value=self.data.get('jwks',''),style='green'), VSplit([ - Label(text="id_token"), + Label(text=_("id_token")), Label(text="a, b, c",style='red'), ]), VSplit([ - Label(text="Access token"), + Label(text=_("Access token")), Label(text="a",style='red'), ]), VSplit([ - Label(text="Userinfo"), + Label(text=_("Userinfo")), Label(text="a, b, c",style='red'), ]), VSplit([ - Label(text="JARM"), + Label(text=_("JARM")), Label(text="a, b, c",style='red'), ]), VSplit([ - Label(text="Request Object"), + Label(text=_("Request Object")), Label(text="a, b, c",style='red'), ]), @@ -312,7 +316,7 @@ def allow_spontaneous_changed(cb): self.spontaneous_scopes.me.read_only = not cb.checked self.spontaneous_scopes = self.myparent.getTitledText( - "Spontaneos scopes validation regex", + _("Spontaneos scopes validation regex"), name='spontaneousScopes', value=self.data.get('spontaneousScopes',''), read_only=False if 'allowSpontaneousScopes' in self.data and self.data['allowSpontaneousScopes'] else True, @@ -322,9 +326,9 @@ def allow_spontaneous_changed(cb): self.tabs['Advanced Client Properties'] = HSplit([ - self.myparent.getTitledCheckBox("Default Prompt login", name='defaultPromptLogin', checked=self.data.get('defaultPromptLogin'), style='green'), - self.myparent.getTitledCheckBox("Persist Authorizations", name='persistClientAuthorizations', checked=self.data.get('persistClientAuthorizations'), style='green'), - self.myparent.getTitledCheckBox("Allow spontaneos scopes", name='allowSpontaneousScopes', checked=self.data.get('allowSpontaneousScopes'), on_selection_changed=allow_spontaneous_changed, style='green'), + self.myparent.getTitledCheckBox(_("Default Prompt login"), name='defaultPromptLogin', checked=self.data.get('defaultPromptLogin'), style='green'), + self.myparent.getTitledCheckBox(_("Persist Authorizations"), name='persistClientAuthorizations', checked=self.data.get('persistClientAuthorizations'), style='green'), + self.myparent.getTitledCheckBox(_("Allow spontaneos scopes"), name='allowSpontaneousScopes', checked=self.data.get('allowSpontaneousScopes'), on_selection_changed=allow_spontaneous_changed, style='green'), self.spontaneous_scopes, @@ -334,24 +338,24 @@ def allow_spontaneous_changed(cb): # height=3, # style='green'), - self.myparent.getTitledText("Initial Login URI", name='initiateLoginUri', value=self.data.get('initiateLoginUri',''),style='green'), + self.myparent.getTitledText(_("Initial Login URI"), name='initiateLoginUri', value=self.data.get('initiateLoginUri',''),style='green'), - self.myparent.getTitledText("Request URIs", + self.myparent.getTitledText(_("Request URIs"), name='requestUris', value='\n'.join(self.data.get('requestUris', [])), height=3, style='green'), - self.myparent.getTitledText("Default ACR", + self.myparent.getTitledText(_("Default ACR"), name='authorizedAcrValues', value='\n'.join(self.data.get('authorizedAcrValues', [])), height=3, style='green'), - self.myparent.getTitledText("TLS Subject DN", name='x5c', value=self.data.get('x5c',''),style='green'), + self.myparent.getTitledText(_("TLS Subject DN"), name='x5c', value=self.data.get('x5c',''),style='green'), self.myparent.getTitledWidget( - "Client Expiration Date", + _("Client Expiration Date"), name='expirationDate', widget=DateSelectWidget( value=self.data.get('expirationDate', ""),parent=self @@ -366,42 +370,42 @@ def allow_spontaneous_changed(cb): self.tabs['Client Scripts'] = HSplit([ - self.myparent.getTitledText("Spontaneous Scopes", + self.myparent.getTitledText(_("Spontaneous Scopes"), name='spontaneousScopes', value='\n'.join(self.data.get('spontaneousScopes', [])), height=3, style='green'), # --------------------------------------------------------------------------------------# - self.myparent.getTitledText("Update Token", + self.myparent.getTitledText(_("Update Token"), name='updateTokenScriptDns', value='\n'.join(self.data.get('updateTokenScriptDns', [])), height=3, style='green'), # --------------------------------------------------------------------------------------# - self.myparent.getTitledText("Post Authn", + self.myparent.getTitledText(_("Post Authn"), name='postAuthnScripts', value='\n'.join(self.data.get('postAuthnScripts', [])), height=3, style='green'), # --------------------------------------------------------------------------------------# - self.myparent.getTitledText("Introspection", + self.myparent.getTitledText(_("Introspection"), name='introspectionScripts', value='\n'.join(self.data.get('introspectionScripts', [])), height=3, style='green'), # --------------------------------------------------------------------------------------# - self.myparent.getTitledText("Password Grant", + self.myparent.getTitledText(_("Password Grant"), name='dynamicRegistrationAllowedPasswordGrantScopes', value='\n'.join(self.data.get('dynamicRegistrationAllowedPasswordGrantScopes', [])), height=3, style='green'), # --------------------------------------------------------------------------------------# - self.myparent.getTitledText("OAuth Consent", + self.myparent.getTitledText(_("OAuth Consent"), name='consentGatheringScripts', value='\n'.join(self.data.get('consentGatheringScripts', [])), height=3, @@ -420,12 +424,3 @@ def client_dialog_nav_selection_changed(self, selection): def __pt_container__(self): return self.dialog - - def client_dialog_nav_selection_changed(self, selection): - self.left_nav = selection - - def __pt_container__(self): - return self.dialog - - - diff --git a/jans-cli-tui/plugins/010_oxauth/edit_scope_dialog.py b/jans-cli-tui/plugins/010_oxauth/edit_scope_dialog.py old mode 100644 new mode 100755 index b051bc58a63..5c9940d970b --- a/jans-cli-tui/plugins/010_oxauth/edit_scope_dialog.py +++ b/jans-cli-tui/plugins/010_oxauth/edit_scope_dialog.py @@ -42,6 +42,8 @@ from wui_components.jans_dialog_with_nav import JansDialogWithNav from wui_components.jans_side_nav_bar import JansSideNavBar +from multi_lang import _ + class EditScopeDialog: def __init__(self,myparent, **params): @@ -71,16 +73,16 @@ def cancel(): navbar=DynamicContainer(lambda:self.side_NavBar), content=DynamicContainer(lambda: self.myparent.oauth_tabs['clients'][self.myparent.oauth_dialog_nav]), ## can be diffrent button_functions=[ - (accept, "Save"), ## button name is changed to make sure it is another one - (cancel, "Cancel") + (accept, _("Save")), ## button name is changed to make sure it is another one + (cancel, _("Cancel")) ], height=self.myparent.dialog_height, width=self.myparent.dialog_width, ) - ok_button = Button(text="OK", handler=accept) - cancel_button = Button(text="Cancel", handler=cancel) + ok_button = Button(text=_("OK"), handler=accept) + cancel_button = Button(text=_("Cancel"), handler=cancel) buttons = [cancel_button] #if params.get('ok_button'): buttons.insert(0, ok_button) diff --git a/jans-cli-tui/plugins/010_oxauth/main.py b/jans-cli-tui/plugins/010_oxauth/main.py old mode 100644 new mode 100755 index fab5cae2278..a582a6c1435 --- a/jans-cli-tui/plugins/010_oxauth/main.py +++ b/jans-cli-tui/plugins/010_oxauth/main.py @@ -34,9 +34,17 @@ from edit_client_dialog import EditClientDialog from edit_scope_dialog import EditScopeDialog +from multi_lang import _ class Plugin(): + """This is a general class for plugins + """ def __init__(self, app): + """init for Plugin class "oxauth" + + Args: + app (_type_): _description_ + """ self.app = app self.pid = 'oxauth' self.name = 'Auth Server' @@ -50,10 +58,13 @@ def process(self): pass def set_center_frame(self): + """center frame content + """ self.app.center_container = self.oauth_main_container - def oauth_prepare_containers(self): + """prepare the main container (tabs) for the current Plugin + """ self.oauth_data_container = { 'clients' :HSplit([],width=D()), @@ -64,9 +75,9 @@ def oauth_prepare_containers(self): self.oauth_containers['scopes'] = HSplit([ VSplit([ - self.app.getButton(text="Get Scopes", name='oauth:scopes:get', jans_help="Retreive first 10 Scopes", handler=self.oauth_get_scopes), - self.app.getTitledText('Search: ', name='oauth:scopes:search', jans_help='Press enter to perform search'), - self.app.getButton(text="Add Scope", name='oauth:scopes:add', jans_help="To add a new scope press this button"), + self.app.getButton(text=_("Get Scopes"), name='oauth:scopes:get', jans_help=_("Retreive first 10 Scopes"), handler=self.oauth_get_scopes), + self.app.getTitledText(_("Search: "), name='oauth:scopes:search', jans_help=_("Press enter to perform search")), + self.app.getButton(text=_("Add Scope"), name='oauth:scopes:add', jans_help=_("To add a new scope press this button")), ], padding=3, width=D(), @@ -76,9 +87,9 @@ def oauth_prepare_containers(self): self.oauth_containers['clients'] = HSplit([ VSplit([ - self.app.getButton(text="Get Clients", name='oauth:clients:get', jans_help="Retreive first 10 OpenID Connect clients", handler=self.oauth_get_clients), - self.app.getTitledText('Search', name='oauth:clients:search', jans_help='Press enter to perform search', accept_handler=self.search_clients), - self.app.getButton(text="Add Client", name='oauth:clients:add', jans_help="To add a new client press this button", handler=self.add_client), + self.app.getButton(text=_("Get Clients"), name='oauth:clients:get', jans_help=_("Retreive first 10 OpenID Connect clients"), handler=self.oauth_get_clients), + self.app.getTitledText(_("Search"), name='oauth:clients:search', jans_help=_("Press enter to perform search"), accept_handler=self.search_clients), + self.app.getButton(text=_("Add Client"), name='oauth:clients:add', jans_help=_("To add a new client press this button"), handler=self.add_client), ], padding=3, @@ -96,7 +107,9 @@ def oauth_prepare_containers(self): ) def oauth_prepare_navbar(self): - self.oauth_navbar = JansNavBar( + """prepare the navbar for the current Plugin + """ + self.oauth_navbar = JansNavBar( self, entries=[('clients', 'Clients'), ('scopes', 'Scopes'), ('keys', 'Keys'), ('defaults', 'Defaults'), ('properties', 'Properties'), ('logging', 'Logging')], selection_changed=self.oauth_nav_selection_changed, @@ -105,14 +118,23 @@ def oauth_prepare_navbar(self): ) def oauth_nav_selection_changed(self, selection): - self.app.logger.debug("OXUATH NAV: %s", selection) + """This method for the selection change + + Args: + selection (str): the current selected tab + """ + self.app.logger.debug('OXUATH NAV: %s', selection) if selection in self.oauth_containers: self.oauth_main_area = self.oauth_containers[selection] else: self.oauth_main_area = self.app.not_implemented - def oauth_update_clients(self, pattern=''): + """update the current clients data to server + + Args: + pattern (str, optional): endpoint arguments for the client data. Defaults to ''. + """ endpoint_args='limit:10' if pattern: endpoint_args='limit:10,pattern:'+pattern @@ -127,17 +149,17 @@ def oauth_update_clients(self, pattern=''): ) except Exception as e: - self.app.show_message("Error getting clients", str(e)) + self.app.show_message(_("Error getting clients"), str(e)) return if rsponse.status_code not in (200, 201): - self.app.show_message("Error getting clients", str(rsponse.text)) + self.app.show_message(_("Error getting clients"), str(rsponse.text)) return try: result = rsponse.json() except Exception: - self.app.show_message("Error getting clients", str(rsponse.text)) + self.app.show_message(_("Error getting clients"), str(rsponse.text)) #press_tab return @@ -177,15 +199,21 @@ def oauth_update_clients(self, pattern=''): get_app().invalidate() else: - self.app.show_message("Oops", "No matching result") - + self.app.show_message(_("Oops"), _("No matching result")) def oauth_get_clients(self): - self.oauth_data_container['clients'] = HSplit([Label("Please wait while getting clients")], width=D()) + """Method to get the clients data from server + """ + self.oauth_data_container['clients'] = HSplit([Label(_("Please wait while getting clients"))], width=D()) t = threading.Thread(target=self.oauth_update_clients, daemon=True) t.start() - def update_oauth_scopes(self, start_index=0): + def oauth_update_scopes(self, start_index=0): + """update the current Scopes data to server + + Args: + start_index (int, optional): add Button("Prev") to the layout. Defaults to 0. + """ try : result = self.app.cli_object.process_command_by_id('get-oauth-scopes', '', 'limit:10', {}) data =[] @@ -227,12 +255,14 @@ def update_oauth_scopes(self, start_index=0): get_app().invalidate() except Exception as e: - self.oauth_data_container['scopes'] = HSplit([Label("Faild to Fitch client Data.. Reason: " + str(e))], width=D()) + self.oauth_data_container['scopes'] = HSplit([Label(_("Faild to Fitch client Data.. Reason: ") + str(e))], width=D()) get_app().invalidate() def oauth_get_scopes(self): - self.oauth_data_container['scopes'] = HSplit([Label("Please wait while getting Scopes")], width=D()) - t = threading.Thread(target=self.update_oauth_scopes, daemon=True) + """Method to get the Scopes data from server + """ + self.oauth_data_container['scopes'] = HSplit([Label(_("Please wait while getting Scopes"))], width=D()) + t = threading.Thread(target=self.oauth_update_scopes, daemon=True) t.start() def display_scope(self): @@ -253,6 +283,14 @@ def edit_client_dialog(self, **params): self.app.show_jans_dialog(dialog) def save_client(self, dialog): + """This method to save the client data to server + + Args: + dialog (_type_): the main dialog to save data in + + Returns: + _type_: bool value to check the status code response + """ self.app.logger.debug(dialog.data) @@ -270,24 +308,34 @@ def save_client(self, dialog): self.oauth_get_clients() return True - self.app.show_message("Error!", "An error ocurred while saving client:\n" + str(response.text)) + self.app.show_message(_("Error!"), _("An error ocurred while saving client:\n") + str(response.text)) def search_clients(self, tbuffer): if not len(tbuffer.text) > 2: - self.app.show_message("Error!", "Search string should be at least three characters") + self.app.show_message(_("Error!"), _("Search string should be at least three characters")) return t = threading.Thread(target=self.oauth_update_clients, args=(tbuffer.text,), daemon=True) t.start() - def add_client(self): - dialog = EditClientDialog(self.app, title="Add Client", data={}, save_handler=self.save_client) + """Method to display the dialog of clients + """ + dialog = EditClientDialog(self.app, title=_("Add Client"), data={}, save_handler=self.save_client) result = self.app.show_jans_dialog(dialog) def delete_client(self, selected, event): + """This method for the deletion of the clients data + + Args: + selected (_type_): The selected Client + event (_type_): _description_ + + Returns: + str: The server response + """ - dialog = self.app.get_confirm_dialog("Are you sure want to delete client inum:\n {} ?".format(selected[0])) + dialog = self.app.get_confirm_dialog(_("Are you sure want to delete client inum:")+"\n {} ?".format(selected[0])) async def coroutine(): focused_before = self.app.layout.current_window diff --git a/jans-cli-tui/plugins/020_fido/main.py b/jans-cli-tui/plugins/020_fido/main.py old mode 100644 new mode 100755 index 5faf5f5ee7c..c08b319a292 --- a/jans-cli-tui/plugins/020_fido/main.py +++ b/jans-cli-tui/plugins/020_fido/main.py @@ -6,7 +6,14 @@ from prompt_toolkit.widgets import Button, Label, Frame class Plugin(): + """This is a general class for plugins + """ def __init__(self, app): + """init for Plugin class "fido" + + Args: + app (_type_): _description_ + """ self.app = app self.pid = 'fido' self.name = 'FIDO' @@ -15,6 +22,8 @@ def process(self): pass def set_center_frame(self): + """center frame content + """ self.app.center_container = Frame( body=HSplit([Label(text="Plugin {} is not imlemented yet".format(self.name)), Button(text="Button-{}".format(self.pid))], width=D()), height=D()) diff --git a/jans-cli-tui/plugins/030_scim/main.py b/jans-cli-tui/plugins/030_scim/main.py old mode 100644 new mode 100755 index d1f1627b87a..c601dcf5ef7 --- a/jans-cli-tui/plugins/030_scim/main.py +++ b/jans-cli-tui/plugins/030_scim/main.py @@ -6,7 +6,14 @@ from prompt_toolkit.widgets import Button, Label, Frame class Plugin(): + """This is a general class for plugins + """ def __init__(self, app): + """init for Plugin class "scim" + + Args: + app (_type_): _description_ + """ self.app = app self.pid = 'scim' self.name = 'SCIM' @@ -15,6 +22,8 @@ def process(self): pass def set_center_frame(self): + """center frame content + """ self.app.center_container = Frame( body=HSplit([Label(text="Plugin {} is not imlemented yet".format(self.name)), Button(text="Button-{}".format(self.pid))], width=D()), height=D()) diff --git a/jans-cli-tui/plugins/040_config_api/main.py b/jans-cli-tui/plugins/040_config_api/main.py old mode 100644 new mode 100755 index 5f5c49babeb..fa464d526cd --- a/jans-cli-tui/plugins/040_config_api/main.py +++ b/jans-cli-tui/plugins/040_config_api/main.py @@ -6,7 +6,14 @@ from prompt_toolkit.widgets import Button, Label, Frame class Plugin(): + """This is a general class for plugins + """ def __init__(self, app): + """init for Plugin class "config_api" + + Args: + app (_type_): _description_ + """ self.app = app self.pid = 'config_api' self.name = 'Config-API' @@ -15,6 +22,8 @@ def process(self): pass def set_center_frame(self): + """center frame content + """ self.app.center_container = Frame( body=HSplit([Label(text="Plugin {} is not imlemented yet".format(self.name)), Button(text="Button-{}".format(self.pid))], width=D()), height=D()) diff --git a/jans-cli-tui/plugins/050_client_api/main.py b/jans-cli-tui/plugins/050_client_api/main.py old mode 100644 new mode 100755 index 24f3cc35cc0..26d5a0c09ec --- a/jans-cli-tui/plugins/050_client_api/main.py +++ b/jans-cli-tui/plugins/050_client_api/main.py @@ -6,7 +6,14 @@ from prompt_toolkit.widgets import Button, Label, Frame class Plugin(): + """This is a general class for plugins + """ def __init__(self, app): + """init for Plugin class "client_api" + + Args: + app (_type_): _description_ + """ self.app = app self.pid = 'client_api' self.name = 'Client-API' @@ -15,6 +22,8 @@ def process(self): pass def set_center_frame(self): + """center frame content + """ self.app.center_container = Frame( body=HSplit([Label(text="Plugin {} is not imlemented yet".format(self.name)), Button(text="Button-{}".format(self.pid))], width=D()), height=D()) diff --git a/jans-cli-tui/plugins/060_scripts/main.py b/jans-cli-tui/plugins/060_scripts/main.py old mode 100644 new mode 100755 index fd3d508fe2e..35f29befa99 --- a/jans-cli-tui/plugins/060_scripts/main.py +++ b/jans-cli-tui/plugins/060_scripts/main.py @@ -6,7 +6,14 @@ from prompt_toolkit.widgets import Button, Label, Frame class Plugin(): + """This is a general class for plugins + """ def __init__(self, app): + """init for Plugin class "scripts" + + Args: + app (_type_): _description_ + """ self.app = app self.pid = 'scripts' self.name = 'Scripts' @@ -15,6 +22,8 @@ def process(self): pass def set_center_frame(self): + """center frame content + """ self.app.center_container = Frame( body=HSplit([Label(text="Plugin {} is not imlemented yet".format(self.name)), Button(text="Button-{}".format(self.pid))], width=D()), height=D()) diff --git a/jans-cli-tui/preview.ipynb b/jans-cli-tui/preview.ipynb old mode 100644 new mode 100755 diff --git a/jans-cli-tui/static.py b/jans-cli-tui/static.py old mode 100644 new mode 100755 diff --git a/jans-cli-tui/utils.py b/jans-cli-tui/utils.py old mode 100644 new mode 100755 diff --git a/jans-cli-tui/wrapper_test.py b/jans-cli-tui/wrapper_test.py old mode 100644 new mode 100755 diff --git a/jans-cli-tui/wui_components/jans_cli_dialog.py b/jans-cli-tui/wui_components/jans_cli_dialog.py old mode 100644 new mode 100755 index 8c89150ba99..bb652b74c3d --- a/jans-cli-tui/wui_components/jans_cli_dialog.py +++ b/jans-cli-tui/wui_components/jans_cli_dialog.py @@ -1,10 +1,11 @@ import json from functools import partial from asyncio import Future - from prompt_toolkit.widgets import Button, Dialog from prompt_toolkit.layout.dimension import D +from multi_lang import _ + class JansGDialog: """This is the main dialog Class Widget for all Jans-cli-tui dialogs except custom dialogs like dialogs with navbar """ @@ -29,7 +30,7 @@ def __init__(self, parent, title, body, buttons=[], width=None): width = int(parent.output.get_size().columns * 0.85) if not buttons: - buttons = [Button(text="OK")] + buttons = [Button(text=_("OK"))] def do_handler(button_text, handler): if handler: diff --git a/jans-cli-tui/wui_components/jans_data_picker.py b/jans-cli-tui/wui_components/jans_data_picker.py old mode 100644 new mode 100755 index 2e5bb55adfa..cf621dfac1b --- a/jans-cli-tui/wui_components/jans_data_picker.py +++ b/jans-cli-tui/wui_components/jans_data_picker.py @@ -306,6 +306,7 @@ def __init__(self, value,parent): # ex: value = "2023-11-27T14:05:35" """init for DateSelectWidget Args: + parent (widget): This is the parent widget for the dialog, to access `Pageup` and `Pagedown` value (str): string time stamp value like "2023-11-27T14:05:35" """ self.parent = parent diff --git a/jans-cli-tui/wui_components/jans_dialog.py b/jans-cli-tui/wui_components/jans_dialog.py old mode 100644 new mode 100755 diff --git a/jans-cli-tui/wui_components/jans_dialog_with_nav.py b/jans-cli-tui/wui_components/jans_dialog_with_nav.py old mode 100644 new mode 100755 index 2e0cae56624..82f90ce7c70 --- a/jans-cli-tui/wui_components/jans_dialog_with_nav.py +++ b/jans-cli-tui/wui_components/jans_dialog_with_nav.py @@ -12,8 +12,6 @@ VerticalLine, ) from prompt_toolkit.key_binding import KeyBindings - - from prompt_toolkit.layout import ScrollablePane from prompt_toolkit.application.current import get_app @@ -93,13 +91,13 @@ def get_nav_bar_key_bindings(self): """ kb = KeyBindings() - @kb.add("pageup", eager=True) ### eager neglect any other keybinding + @kb.add('pageup', eager=True) ### eager neglect any other keybinding def _go_up(event) -> None: app = get_app() self.navbar.go_up() app.layout.focus(self.navbar) - @kb.add("pagedown", eager=True) + @kb.add('pagedown', eager=True) def _go_up(event) -> None: app = get_app() self.navbar.go_down() diff --git a/jans-cli-tui/wui_components/jans_drop_down.py b/jans-cli-tui/wui_components/jans_drop_down.py old mode 100644 new mode 100755 index aa2c4d16111..252210a1cde --- a/jans-cli-tui/wui_components/jans_drop_down.py +++ b/jans-cli-tui/wui_components/jans_drop_down.py @@ -8,6 +8,7 @@ from prompt_toolkit.key_binding.bindings.focus import focus_next from prompt_toolkit.layout.dimension import D + class JansSelectBox: """_summary_ """ @@ -38,7 +39,7 @@ def __init__(self, values=[], value=None, height=4, rotatable_up=True, rotatable height=self.height, cursorline=False, width=D(), #15, - style="bg:#4D4D4D", + style='bg:#4D4D4D', right_margins=[ScrollbarMargin(display_arrows=True),], wrap_lines=True, allow_scroll_beyond_bottom=True, @@ -194,16 +195,16 @@ def _enter(event) -> None: self.value = self.select_box.value - @kb.add("up") + @kb.add('up') def _up(event): self.select_box.up() - @kb.add("down") + @kb.add('down') def _up(event): self.select_box.down() - @kb.add("escape") - @kb.add("tab") + @kb.add('escape') + @kb.add('tab') def _(event): if self.select_box_float in get_app().layout.container.floats: get_app().layout.container.floats.remove(self.select_box_float) diff --git a/jans-cli-tui/wui_components/jans_message_dialog.py b/jans-cli-tui/wui_components/jans_message_dialog.py old mode 100644 new mode 100755 index d1282dc4d2f..8a1cae25492 --- a/jans-cli-tui/wui_components/jans_message_dialog.py +++ b/jans-cli-tui/wui_components/jans_message_dialog.py @@ -4,6 +4,7 @@ from prompt_toolkit.application.current import get_app from prompt_toolkit.layout.dimension import D +from multi_lang import _ class JansMessageDialog: """This is a Dialog to show Message @@ -25,7 +26,7 @@ def __init__(self, title, body, buttons=[], focus_on_exit=None): self.me = None self.focus_on_exit = focus_on_exit if not buttons: - buttons = ["OK"] + buttons = [_("OK")] def exit_me(result, handler): if handler: diff --git a/jans-cli-tui/wui_components/jans_nav_bar.py b/jans-cli-tui/wui_components/jans_nav_bar.py old mode 100644 new mode 100755 index dbbfdde6246..b73cf5df805 --- a/jans-cli-tui/wui_components/jans_nav_bar.py +++ b/jans-cli-tui/wui_components/jans_nav_bar.py @@ -78,12 +78,12 @@ def get_nav_bar_key_bindings(self): """ kb = KeyBindings() - @kb.add("left") + @kb.add('left') def _go_up(event) -> None: self.cur_navbar_selection = (self.cur_navbar_selection - 1) % len(self.navbar_entries) self._set_selection() - @kb.add("right") + @kb.add('right') def _go_up(event) -> None: self.cur_navbar_selection = (self.cur_navbar_selection + 1) % len(self.navbar_entries) self._set_selection() diff --git a/jans-cli-tui/wui_components/jans_side_nav_bar.py b/jans-cli-tui/wui_components/jans_side_nav_bar.py old mode 100644 new mode 100755 index eda617f7a80..83946c476f2 --- a/jans-cli-tui/wui_components/jans_side_nav_bar.py +++ b/jans-cli-tui/wui_components/jans_side_nav_bar.py @@ -52,7 +52,7 @@ def create_window(self): style=self.entries_color, ), - style="class:select-box", + style='class:select-box', height=Dimension(preferred=len( self.navbar_entries)*2, max=len(self.navbar_entries)*2+1), cursorline=True, @@ -80,10 +80,10 @@ def get_navbar_entries(self): result = [] for i, entry in enumerate(self.navbar_entries): if i == self.cur_navbar_selection: - result.append([("[SetCursorPosition]", "")]) + result.append([('[SetCursorPosition]', '')]) result.append(entry) - result.append("\n") - result.append("\n") + result.append('\n') + result.append('\n') return merge_formatted_text(result) def update_selection(self): @@ -110,13 +110,13 @@ def get_nav_bar_key_bindings(self): """ kb = KeyBindings() - @kb.add("up") + @kb.add('up') def _go_up(event) -> None: self.cur_navbar_selection = ( self.cur_navbar_selection - 1) % len(self.navbar_entries) self.update_selection() - @kb.add("down") + @kb.add('down') def _go_down(event) -> None: self.cur_navbar_selection = ( self.cur_navbar_selection + 1) % len(self.navbar_entries) diff --git a/jans-cli-tui/wui_components/jans_vetrical_nav.py b/jans-cli-tui/wui_components/jans_vetrical_nav.py old mode 100644 new mode 100755 index a4e96740a32..5e4b938446c --- a/jans-cli-tui/wui_components/jans_vetrical_nav.py +++ b/jans-cli-tui/wui_components/jans_vetrical_nav.py @@ -92,7 +92,7 @@ def create_window(self): key_bindings=self._get_key_bindings(), style=self.headerColor, ), - style="class:select-box", + style='class:select-box', height=D(preferred=1, max=1), cursorline=False, ), @@ -104,7 +104,7 @@ def create_window(self): key_bindings=self._get_key_bindings(), style=self.entriesColor, ), - style="class:select-box", + style='class:select-box', height=D(preferred=len(self.data), max=len(self.data)), cursorline=True, right_margins=[ScrollbarMargin(display_arrows=True), ], @@ -169,7 +169,7 @@ def _get_head_text(self): y += self.headers[k] + ' ' * \ (self.spaces[k] - len(self.headers[k]) + 5) result.append(y) - result.append("\n") + result.append('\n') return merge_formatted_text(result) @@ -182,10 +182,10 @@ def _get_formatted_text(self): result = [] for i, entry in enumerate(self.mod_data): ## entry = ['1800.6c5faa', 'Jans Config Api Client', 'authorization_code,refresh_...', 'Reference] if i == self.selectes: - result.append([("[SetCursorPosition]", "")]) + result.append([('[SetCursorPosition]', '')]) result.append(' '.join(entry)) - result.append("\n") + result.append('\n') return merge_formatted_text(result) @@ -197,22 +197,22 @@ def _get_key_bindings(self): """ kb = KeyBindings() - @kb.add("up") + @kb.add('up') def _go_up(event) -> None: self.selectes = (self.selectes - 1) % len(self.data) - @kb.add("down") + @kb.add('down') def _go_up(event) -> None: self.selectes = (self.selectes + 1) % len(self.data) - @kb.add("enter") + @kb.add('enter') def _(event): passed = [i.strip() for i in self.data[self.selectes]] size = self.myparent.output.get_size() self.on_enter(passed=passed,event=event,size=size,data=self.all_data[self.selectes]) - @kb.add("d") + @kb.add('d') def _(event): selected_line = [i.strip() for i in self.data[self.selectes]] size = self.myparent.output.get_size() @@ -224,7 +224,7 @@ def _(event): data=self.all_data[self.selectes]) - @kb.add("delete") + @kb.add('delete') def _(event): if self.on_delete: selected_line = [i.strip() for i in self.data[self.selectes]] From 130be7d8ea7b674b4f5af20763bdafa3f017f65f Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Mon, 12 Sep 2022 11:38:37 +0300 Subject: [PATCH 104/364] fix: jans-cli verification url string --- jans-cli-tui/jans-cli-tui.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index dfd1bdaad99..e94f921ec6c 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -200,9 +200,8 @@ def create_cli(self): response = self.cli_object.get_device_verification_code() result = response.json() - msg = _("Please visit verification url")+str(result['verification_uri'])+_("and enter user code ")+str(result['user_code'])+_(" in ")+str(result['expires_in'])+_(" secods") - - body = HSplit([Label(msg)]) + msg = _("Please visit verification url %s and enter user code %s in %d seconds.") + body = HSplit([Label(msg % (result['verification_uri'], result['user_code'], result['expires_in']))]) dialog = JansGDialog(self, title=_("Waiting Response"), body=body) async def coroutine(): From 0aadfce393115e2cdb3efadcd1383ff510726344 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Mon, 12 Sep 2022 12:45:23 +0300 Subject: [PATCH 105/364] fix: jans-cli handle Jwt Signature is Invalid --- jans-cli-tui/cli/config_cli.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/jans-cli-tui/cli/config_cli.py b/jans-cli-tui/cli/config_cli.py index 49624217e95..62bd74c5275 100755 --- a/jans-cli-tui/cli/config_cli.py +++ b/jans-cli-tui/cli/config_cli.py @@ -328,6 +328,7 @@ def get_request_header(self, headers={}, access_token=None): def check_connection(self): + self.cli_logger.debug("Checking connection") url = 'https://{}/jans-auth/restv1/token'.format(self.idp_host) try: @@ -339,6 +340,7 @@ def check_connection(self): cert=self.mtls_client_cert ) except Exception as e: + self.cli_logger.error(str(e)) if self.wrapped: return str(e) @@ -363,7 +365,9 @@ def check_connection(self): ) if not response.status_code == 200: + config['DEFAULT']['access_token_enc'] = '' self.access_token = None + write_config() return response.text return True @@ -1387,7 +1391,6 @@ def main(): cli_object.get_sample_schema(args.schema) elif args.operation_id: cli_object.process_command_by_id(args.operation_id, args.url_suffix, args.endpoint_args, args.data) - print() #except Exception as e: # print(u"\u001b[38;5;{}mAn Unhandled error raised: {}\u001b[0m".format(error_color, e)) From 5e70e407c952a0cc826d43b7342689907fcb91d2 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Mon, 12 Sep 2022 13:21:28 +0300 Subject: [PATCH 106/364] fix: jans-cli getting scopes --- jans-cli-tui/plugins/010_oxauth/main.py | 79 ++++++++++++++++--------- jans-cli-tui/wrapper_test.py | 5 +- 2 files changed, 53 insertions(+), 31 deletions(-) diff --git a/jans-cli-tui/plugins/010_oxauth/main.py b/jans-cli-tui/plugins/010_oxauth/main.py index a582a6c1435..d86b0f8d101 100755 --- a/jans-cli-tui/plugins/010_oxauth/main.py +++ b/jans-cli-tui/plugins/010_oxauth/main.py @@ -135,9 +135,9 @@ def oauth_update_clients(self, pattern=''): Args: pattern (str, optional): endpoint arguments for the client data. Defaults to ''. """ - endpoint_args='limit:10' + endpoint_args ='limit:10' if pattern: - endpoint_args='limit:10,pattern:'+pattern + endpoint_args +=',pattern:'+pattern try : rsponse = self.app.cli_object.process_command_by_id( @@ -215,24 +215,39 @@ def oauth_update_scopes(self, start_index=0): start_index (int, optional): add Button("Prev") to the layout. Defaults to 0. """ try : - result = self.app.cli_object.process_command_by_id('get-oauth-scopes', '', 'limit:10', {}) - data =[] - for d in result: - data.append( - [ - d['id'], - d['description'], - d['scopeType'] - ] - ) + rsponse = self.app.cli_object.process_command_by_id('get-oauth-scopes', '', 'limit:10', {}) + except Exception as e: + self.app.show_message(_("Error getting scopes"), str(e)) + return - clients = JansVerticalNav( - myparent=self, + if rsponse.status_code not in (200, 201): + self.app.show_message(_("Error getting scopes"), str(rsponse.text)) + return + + try: + result = rsponse.json() + except Exception: + self.app.show_message(_("Error getting scopes"), str(rsponse.text)) + return + + + data =[] + for d in result: + data.append( + [ + d['id'], + d['description'], + d['scopeType'] + ] + ) + + scopes = JansVerticalNav( + myparent=self.app, headers=['id', 'Description', 'Type'], preferred_size= [0,0,30,0], data=data, on_enter=self.edit_scope_dialog, - on_display=self.data_display_dialog, + on_display=self.app.data_display_dialog, # selection_changed=self.data_selection_changed, selectes=0, headerColor='green', @@ -240,23 +255,21 @@ def oauth_update_scopes(self, start_index=0): all_data=result ) - buttons = [] - if start_index > 0: - buttons.append(Button("Prev")) - if len(result) >= 10: - buttons.append(Button("Next")) + buttons = [] + if start_index > 0: + buttons.append(Button("Prev")) + if len(result) >= 10: + buttons.append(Button("Next")) - self.layout.focus(clients) # clients.focuse..!? TODO >> DONE - self.oauth_data_container['scopes'] = HSplit([ - clients, - VSplit(buttons, padding=5, align=HorizontalAlign.CENTER) - ]) + self.app.layout.focus(scopes) # clients.focuse..!? TODO >> DONE + self.oauth_data_container['scopes'] = HSplit([ + scopes, + VSplit(buttons, padding=5, align=HorizontalAlign.CENTER) + ]) + + get_app().invalidate() - get_app().invalidate() - except Exception as e: - self.oauth_data_container['scopes'] = HSplit([Label(_("Faild to Fitch client Data.. Reason: ") + str(e))], width=D()) - get_app().invalidate() def oauth_get_scopes(self): """Method to get the Scopes data from server @@ -357,3 +370,11 @@ async def coroutine(): return result ensure_future(coroutine()) + + + def edit_scope_dialog(self, **params): + selected_line_data = params['data'] + title = _("Edit Scopes") + + dialog = EditScopeDialogDialog(self.app, title=title, data=selected_line_data, save_handler=self.save_client) + self.app.show_jans_dialog(dialog) diff --git a/jans-cli-tui/wrapper_test.py b/jans-cli-tui/wrapper_test.py index 7cb8c519e79..d918c5131ff 100755 --- a/jans-cli-tui/wrapper_test.py +++ b/jans-cli-tui/wrapper_test.py @@ -52,7 +52,7 @@ data_fn='', data={} ) -""" + result = cli_object.process_command_by_id( operation_id='get-oauth-openid-clients', url_suffix='', @@ -61,7 +61,8 @@ data={} ) +""" - +result = cli_object.process_command_by_id('get-oauth-scopes', '', 'limit:10', {}) print(result) From d41a31343e82eb8af2101d029990caf38ff31d14 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Mon, 12 Sep 2022 21:53:55 +0300 Subject: [PATCH 107/364] fix: jans-cli client Encryption/Signing tab --- jans-cli-tui/.#wrapper_test.py | 1 + jans-cli-tui/cli/config_cli.py | 18 ++++++ jans-cli-tui/jans-cli-tui.py | 3 + .../plugins/010_oxauth/edit_client_dialog.py | 55 +++++++++++-------- 4 files changed, 54 insertions(+), 23 deletions(-) create mode 120000 jans-cli-tui/.#wrapper_test.py diff --git a/jans-cli-tui/.#wrapper_test.py b/jans-cli-tui/.#wrapper_test.py new file mode 120000 index 00000000000..94c26d549aa --- /dev/null +++ b/jans-cli-tui/.#wrapper_test.py @@ -0,0 +1 @@ +mbaser@ubuntu.9690 \ No newline at end of file diff --git a/jans-cli-tui/cli/config_cli.py b/jans-cli-tui/cli/config_cli.py index 62bd74c5275..986dd44b197 100755 --- a/jans-cli-tui/cli/config_cli.py +++ b/jans-cli-tui/cli/config_cli.py @@ -222,6 +222,7 @@ def __init__(self, host, client_id, client_secret, access_token, test_client=Fal self.wrapped = __name__ != "__main__" self.access_token = access_token or config['DEFAULT'].get('access_token') self.jwt_validation_url = 'https://{}/jans-config-api/api/v1/acrs'.format(self.idp_host) + self.discovery_endpoint = '/.well-known/openid-configuration' self.set_user() self.plugins() @@ -370,6 +371,23 @@ def check_connection(self): write_config() return response.text + try: + response = requests.get( + url = 'https://{}{}'.format(self.idp_host, self.discovery_endpoint), + headers=self.get_request_header({'Accept': 'application/json'}), + verify=self.verify_ssl, + cert=self.mtls_client_cert + ) + except Exception as e: + self.cli_logger.error(str(e)) + if self.wrapped: + return str(e) + + raise ValueError( + self.colored_text("Unable to get OpenID configuration:\n {}".format(str(e)), error_color)) + + self.openid_configuration = response.json() + return True diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index e94f921ec6c..e1713d4f50a 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -188,6 +188,8 @@ def create_cli(self): status = self.cli_object.check_connection() + self.logger.info("OpenID Configuration: %s", self.cli_object.openid_configuration) + self.press_tab() if status not in (True, 'ID Token is expired'): @@ -411,6 +413,7 @@ def getTitledRadioButton(self, title, name, values, current_value=None, jans_hel def getTitledWidget(self, title, name, widget, jans_help='', style=''): + title += ': ' widget.window.jans_name = name widget.window.jans_help = jans_help #li, w2, width = self.handle_long_string(title, widget.values, widget) diff --git a/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py b/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py index a7e7cc7c496..faf45f793ff 100755 --- a/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py +++ b/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py @@ -283,32 +283,41 @@ def prepare_tabs(self): ] ) - self.tabs['Encryption/Signing'] = HSplit([ + + encryption_signing = [ self.myparent.getTitledText(title =_("Client JWKS URI"), name='jwksUri', value=self.data.get('jwksUri',''),style='green'), self.myparent.getTitledText(title =_("Client JWKS"), name='jwks', value=self.data.get('jwks',''),style='green'), - VSplit([ - Label(text=_("id_token")), - Label(text="a, b, c",style='red'), - ]), - VSplit([ - Label(text=_("Access token")), - Label(text="a",style='red'), - ]), - VSplit([ - Label(text=_("Userinfo")), - Label(text="a, b, c",style='red'), - ]), - VSplit([ - Label(text=_("JARM")), - Label(text="a, b, c",style='red'), - ]), - VSplit([ - Label(text=_("Request Object")), - Label(text="a, b, c",style='red'), - ]), - ] - ) + + + for title, swagger_key, openid_key in ( + + (_("ID Token Alg for Signing "), 'idTokenSignedResponseAlg', 'id_token_signing_alg_values_supported'), + (_("ID Token Alg for Encryption"), 'idTokenEncryptedResponseAlg', 'id_token_encryption_alg_values_supported'), + (_("ID Token Enc for Encryption"), 'idTokenEncryptedResponseEnc', 'id_token_encryption_enc_values_supported'), + #(_("Access Token Alg for Signing "), 'accessTokenSigningAlg', 'id_token_signing_alg_values_supported'), #?? openid key + + (_("User Info for Signing "), 'userInfoSignedResponseAlg', 'userinfo_signing_alg_values_supported'), + (_("User Info Alg for Encryption"), 'userInfoEncryptedResponseAlg', 'userinfo_encryption_alg_values_supported'), + (_("User Info Enc for Encryption"), 'userInfoEncryptedResponseEnc', 'userinfo_encryption_enc_values_supported'), + + (_("Request Object Alg for Signing "), 'requestObjectSigningAlg', 'request_object_signing_alg_values_supported'), + (_("Request Object Alg for Encryption"), 'requestObjectEncryptionAlg', 'request_object_encryption_alg_values_supported'), + (_("Request Object Enc for Encryption"), 'requestObjectEncryptionEnc', 'request_object_encryption_enc_values_supported'), + ): + + encryption_signing.append(self.myparent.getTitledWidget( + title, + name=swagger_key, + widget=DropDownWidget( + values=[ (alg, alg) for alg in self.myparent.cli_object.openid_configuration[openid_key] ], + value=self.data.get(swagger_key) + ), + jans_help=self.myparent.get_help_from_schema(schema, swagger_key), + style='green')) + + + self.tabs['Encryption/Signing'] = HSplit(encryption_signing) def allow_spontaneous_changed(cb): self.spontaneous_scopes.me.window.style = 'underline ' + (self.myparent.styles['textarea'] if cb.checked else self.myparent.styles['textarea-readonly']) From d76d8cb2f17960d988ed71aba6fb0ba4e98863a1 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Mon, 12 Sep 2022 21:54:33 +0300 Subject: [PATCH 108/364] fix: jans-cli remove temp file --- jans-cli-tui/.#wrapper_test.py | 1 - 1 file changed, 1 deletion(-) delete mode 120000 jans-cli-tui/.#wrapper_test.py diff --git a/jans-cli-tui/.#wrapper_test.py b/jans-cli-tui/.#wrapper_test.py deleted file mode 120000 index 94c26d549aa..00000000000 --- a/jans-cli-tui/.#wrapper_test.py +++ /dev/null @@ -1 +0,0 @@ -mbaser@ubuntu.9690 \ No newline at end of file From 8c8a9e901929ec0560f0c4709a64f758b07b243e Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Tue, 13 Sep 2022 10:25:50 +0300 Subject: [PATCH 109/364] fix: jans-cli default openid_configuration --- jans-cli-tui/cli/config_cli.py | 1 + 1 file changed, 1 insertion(+) diff --git a/jans-cli-tui/cli/config_cli.py b/jans-cli-tui/cli/config_cli.py index 986dd44b197..6d5316fd9bb 100755 --- a/jans-cli-tui/cli/config_cli.py +++ b/jans-cli-tui/cli/config_cli.py @@ -223,6 +223,7 @@ def __init__(self, host, client_id, client_secret, access_token, test_client=Fal self.access_token = access_token or config['DEFAULT'].get('access_token') self.jwt_validation_url = 'https://{}/jans-config-api/api/v1/acrs'.format(self.idp_host) self.discovery_endpoint = '/.well-known/openid-configuration' + self.openid_configuration = {} self.set_user() self.plugins() From d0f2822849fe83a18c4a12159bba40f55f3ed6e9 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Tue, 13 Sep 2022 09:21:07 -0700 Subject: [PATCH 110/364] fix:jans-cli up and down keys + selectOne --- jans-cli-tui/wui_components/jans_drop_down.py | 55 +++++++++++-------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/jans-cli-tui/wui_components/jans_drop_down.py b/jans-cli-tui/wui_components/jans_drop_down.py index 252210a1cde..72bfce0672a 100755 --- a/jans-cli-tui/wui_components/jans_drop_down.py +++ b/jans-cli-tui/wui_components/jans_drop_down.py @@ -12,6 +12,7 @@ class JansSelectBox: """_summary_ """ + def __init__(self, values=[], value=None, height=4, rotatable_up=True, rotatable_down=True): """_summary_ @@ -22,25 +23,29 @@ def __init__(self, values=[], value=None, height=4, rotatable_up=True, rotatable rotatable_up (bool, optional): _description_. Defaults to True. rotatable_down (bool, optional): _description_. Defaults to True. """ + self.values = values + self.set_value(value) + # if not self.values : + # --------------------------------------------------- # - self.height=min(len(self.values), height) + self.height = min(len(self.values), height) self.rotatable_up = rotatable_up self.rotatable_down = rotatable_down # --------------------------------------------------- # - self.container =HSplit(children=[ Window( + self.container = HSplit(children=[Window( content=FormattedTextControl( text=self._get_formatted_text, focusable=True, ), height=self.height, cursorline=False, - width=D(), #15, + width=D(), # 15, style='bg:#4D4D4D', - right_margins=[ScrollbarMargin(display_arrows=True),], + right_margins=[ScrollbarMargin(display_arrows=True), ], wrap_lines=True, allow_scroll_beyond_bottom=True, )]) @@ -69,14 +74,15 @@ def _get_formatted_text(self): result = [] for i, entry in enumerate(self.values): if i == self.selected_line: - result.append(HTML(''.format('#ADD8E6', entry[1]))) + result.append( + HTML(''.format('#ADD8E6', entry[1]))) else: result.append(HTML('{}'.format(entry[1]))) result.append("\n") return merge_formatted_text(result) - def shift(self,seq, n): + def shift(self, seq, n): """_summary_ Args: @@ -91,28 +97,26 @@ def shift(self,seq, n): def up(self): """_summary_ """ - if self.selected_line == 0 : - if self.rotatable_up and self.values[self.selected_line] == self.values[0]: + if self.selected_line == 0: + if self.rotatable_up and self.values[self.selected_line] == self.values[0]: pass - else : - self.values = self.shift(self.values,-1) - else : + else: + self.values = self.shift(self.values, -1) + else: self.selected_line = (self.selected_line - 1) % (self.height) - def down(self): """_summary_ """ - if self.selected_line +1 == (self.height): - if self.rotatable_down and self.values[self.selected_line] == self.values[-1]: + if self.selected_line + 1 == (self.height): + if self.rotatable_down and self.values[self.selected_line] == self.values[-1]: pass else: self.values = self.shift(self.values, 1) - else : + else: self.selected_line = (self.selected_line + 1) % (self.height) - def __pt_container__(self): return self.container @@ -120,6 +124,7 @@ def __pt_container__(self): class DropDownWidget: """This is a Combobox widget (drop down) to select single from multi choices """ + def __init__(self, values=[], value=None): """init for DropDownWidget Args: @@ -132,6 +137,7 @@ def __init__(self, values=[], value=None): value=self.data.get('tokenEndpointAuthMethodsSupported')) """ self.values = values + values.insert(0, (None, 'Select One')) for val in values: if val[0] == value: self.text = val[1] @@ -144,16 +150,18 @@ def __init__(self, values=[], value=None): content=FormattedTextControl( text=self._get_text, focusable=True, - key_bindings=self._get_key_bindings(), - ), height=D()) #5 ## large sized enties get >> (window too small) + key_bindings=self._get_key_bindings(), + ), height=D()) # 5 ## large sized enties get >> (window too small) - self.select_box = JansSelectBox(values=self.values, value=value, rotatable_down=True, rotatable_up=True, height=4) - self.select_box_float = Float(content=self.select_box, xcursor=True, ycursor=True) + self.select_box = JansSelectBox( + values=self.values, value=value, rotatable_down=True, rotatable_up=True, height=4) + self.select_box_float = Float( + content=self.select_box, xcursor=True, ycursor=True) @property def value(self): """Getter for the value property - + Returns: str: The selected value """ @@ -186,10 +194,13 @@ def _focus_next(event): @kb.add("enter") def _enter(event) -> None: - if self.select_box_float not in get_app().layout.container.floats: get_app().layout.container.floats.append(self.select_box_float) else: + # if self.select_box.values[self.select_box.selected_line][1] == "Select One": + # self.text = "Enter to Select" + # get_app().layout.container.floats.remove(self.select_box_float) + # else : self.text = self.select_box.values[self.select_box.selected_line][1] get_app().layout.container.floats.remove(self.select_box_float) From 9b0e31d14959c0e82082d5801d1fe6d4e44e6577 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Tue, 13 Sep 2022 09:22:44 -0700 Subject: [PATCH 111/364] feat:jans-cli add scope tabs --- .../plugins/010_oxauth/edit_scope_dialog.py | 223 +++++++++++++++--- 1 file changed, 196 insertions(+), 27 deletions(-) diff --git a/jans-cli-tui/plugins/010_oxauth/edit_scope_dialog.py b/jans-cli-tui/plugins/010_oxauth/edit_scope_dialog.py index 5c9940d970b..5e7e4a25c96 100755 --- a/jans-cli-tui/plugins/010_oxauth/edit_scope_dialog.py +++ b/jans-cli-tui/plugins/010_oxauth/edit_scope_dialog.py @@ -41,56 +41,225 @@ ) from wui_components.jans_dialog_with_nav import JansDialogWithNav from wui_components.jans_side_nav_bar import JansSideNavBar +from utils import DialogUtils -from multi_lang import _ +from wui_components.jans_cli_dialog import JansGDialog +from wui_components.jans_drop_down import DropDownWidget -class EditScopeDialog: - def __init__(self,myparent, **params): - self.myparent = myparent - self.future = Future() +from multi_lang import _ - def accept_text(buf): - get_app().layout.focus(ok_button) - buf.complete_state = None - return True - def accept(): - self.future.set_result(DialogResult.ACCEPT) +class EditScopeDialog(JansGDialog, DialogUtils): + """The Main scope Dialog that contain every thing related to The scope + """ + def __init__(self, parent, title, data, buttons=[], save_handler=None): + """init for `EditscopeDialog`, inherits from two diffrent classes `JansGDialog` and `DialogUtils` + + JansGDialog (dialog): This is the main dialog Class Widget for all Jans-cli-tui dialogs except custom dialogs like dialogs with navbar + DialogUtils (methods): Responsable for all `make data from dialog` and `check required fields` in the form for any Edit or Add New + Args: + parent (widget): This is the parent widget for the dialog, to access `Pageup` and `Pagedown` + title (str): The Main dialog title + data (list): selected line data + button_functions (list, optional): Dialog main buttons with their handlers. Defaults to []. + save_handler (method, optional): _description_. Defaults to None. + """ + super().__init__(parent, title, buttons) + self.save_handler = save_handler + self.data = data + self.prepare_tabs() + + def save(): + + self.data = self.make_data_from_dialog() + self.data['disabled'] = not self.data['disabled'] + for list_key in ( + 'redirectUris', + 'scopes', + 'postLogoutRedirectUris', + 'contacts', + 'authorizedOrigins', + 'rptClaimsScripts', + 'claimRedirectUris', + ): + if self.data[list_key]: + self.data[list_key] = self.data[list_key].splitlines() + + if 'accessTokenAsJwt' in self.data: + self.data['accessTokenAsJwt'] = self.data['accessTokenAsJwt'] == 'jwt' + + if 'rptAsJwt' in self.data: + self.data['rptAsJwt'] = self.data['rptAsJwt'] == 'jwt' + + cfr = self.check_required_fields() + self.myparent.logger.debug('CFR: '+str(cfr)) + if not cfr: + return + + self.myparent.logger.debug('handler: '+str(save_handler)) + close_me = True + if save_handler: + close_me = self.save_handler(self) + if close_me: + self.future.set_result(DialogResult.ACCEPT) + def cancel(): self.future.set_result(DialogResult.CANCEL) - - self.side_NavBar = JansSideNavBar(myparent=self.myparent, - entries=list(self.myparent.oauth_tabs['clients'].keys()), - selection_changed=(self.myparent.client_dialog_nav_selection_changed) , - select=0, + self.side_nav_bar = JansSideNavBar(myparent=self.myparent, + entries=list(self.tabs.keys()), + selection_changed=(self.scope_dialog_nav_selection_changed) , + select=0, entries_color='#2600ff') self.dialog = JansDialogWithNav( - title="Edit Scope Data (Scopes)", - navbar=DynamicContainer(lambda:self.side_NavBar), - content=DynamicContainer(lambda: self.myparent.oauth_tabs['clients'][self.myparent.oauth_dialog_nav]), ## can be diffrent + title=title, + navbar=self.side_nav_bar, + content=DynamicContainer(lambda: self.tabs[self.left_nav]), button_functions=[ - (accept, _("Save")), ## button name is changed to make sure it is another one + (save, _("Save")), (cancel, _("Cancel")) ], height=self.myparent.dialog_height, width=self.myparent.dialog_width, ) + + def prepare_tabs(self): + """Prepare the tabs for Edil scope Dialogs + """ - ok_button = Button(text=_("OK"), handler=accept) - cancel_button = Button(text=_("Cancel"), handler=cancel) - buttons = [cancel_button] - #if params.get('ok_button'): - buttons.insert(0, ok_button) + schema = self.myparent.cli_object.get_schema_from_reference('#/components/schemas/Scope') + self.tabs = OrderedDict() - def __pt_container__(self): - return self.dialog + self.tabs['OAuth'] = HSplit([ + self.myparent.getTitledText(_("id"), name='id', value=self.data.get('id',''), style='green'), + self.myparent.getTitledText(_("inum"), name='inum', value=self.data.get('inum',''), style='green',read_only=True,), + self.myparent.getTitledText(_("Display Name"), name='displayName', value=self.data.get('displayName',''), style='green'), + self.myparent.getTitledText(_("Description"), name='description', value=self.data.get('description',''), style='green'), + + self.myparent.getTitledCheckBox(_("Default Scope"), + name='defaultScope', + checked=self.data.get('defaultScope'), + style='green'), + + self.myparent.getTitledCheckBox(_("Show in configuration endpoint"), + name='showInConfigurationEndpoint', + checked=self.data.get('showInConfigurationEndpoint'), + style='green'), + ],width=D(), + ) + + self.tabs['OpenID'] = HSplit([ + self.myparent.getTitledText(_("id"), name='id', value=self.data.get('id',''), style='green'), + self.myparent.getTitledText(_("inum"), name='inum', value=self.data.get('inum',''), style='green',read_only=True,), + self.myparent.getTitledText(_("Display Name"), name='displayName', value=self.data.get('displayName',''), style='green'), + self.myparent.getTitledText(_("Description"), name='description', value=self.data.get('description',''), style='green'), + + self.myparent.getTitledCheckBox(_("Default Scope"), + name='defaultScope', + checked=self.data.get('defaultScope'), + style='green'), + + self.myparent.getTitledCheckBox(_("Show in configuration endpoint"), + name='showInConfigurationEndpoint', + checked=self.data.get('showInConfigurationEndpoint'), + style='green'), + self.myparent.getTitledText(_("Claims"), + name='claims', + value='\n'.join(self.data.get('claims', [])), + height=3, + style='green'), + # Label(text=_("Claims"),style='red'), ## name = claims TODO + ],width=D(), + ) + + + self.tabs['Dynamic'] = HSplit([ + self.myparent.getTitledText(_("id"), name='id', value=self.data.get('id',''), style='green'), + self.myparent.getTitledText(_("inum"), name='inum', value=self.data.get('inum',''), style='green',read_only=True), + self.myparent.getTitledText(_("Display Name"), name='displayName', value=self.data.get('displayName',''), style='green'), + self.myparent.getTitledText(_("Description"), name='description', value=self.data.get('description',''), style='green'), + + # Label(text=_("Dynamic Scope Script"),style='red'), ## name = dynamicScopeScripts TODO + + self.myparent.getTitledText(_("Dynamic Scope Script"), + name='dynamicScopeScripts', + value='\n'.join(self.data.get('dynamicScopeScripts', [])), + height=3, + style='green'), + + + self.myparent.getTitledCheckBox(_("Allow for dynamic registration"), + name='defaultScope', + checked=self.data.get('defaultScope'), + style='green'), + + self.myparent.getTitledCheckBox(_("Show in configuration endpoint"), + name='showInConfigurationEndpoint', + checked=self.data.get('showInConfigurationEndpoint'), + style='green'), + + self.myparent.getTitledText(_("Claims"), + name='claims', + value='\n'.join(self.data.get('claims', [])), + height=3, + style='green'), + + # Label(text=_("Claims"),style='red'), ## name = claims TODO + + ],width=D(), + ) + + + self.tabs['Spontaneous'] = HSplit([ + self.myparent.getTitledText(_("id"), name='id', value=self.data.get('id',''), style='green',read_only=True,), + self.myparent.getTitledText(_("inum"), name='inum', value=self.data.get('inum',''), style='green',read_only=True,), + self.myparent.getTitledText(_("Display Name"), name='displayName', value=self.data.get('displayName',''), style='green',read_only=True,), + self.myparent.getTitledText(_("Description"), name='description', value=self.data.get('description',''), style='green',read_only=True,), + self.myparent.getTitledText(_("Associated Client"), name='none', value=self.data.get('none',''), style='green',read_only=True,height=3,),## Not fount + self.myparent.getTitledText(_("Creationg time"), name='creationDate', value=self.data.get('creationDate',''), style='green',read_only=True,), + + + ],width=D(), + ) + + + self.tabs['UMA'] = HSplit([ + self.myparent.getTitledText(_("id"), name='id', value=self.data.get('id',''), style='green'), + self.myparent.getTitledText(_("inum"), name='inum', value=self.data.get('inum',''), style='green',read_only=True,), + self.myparent.getTitledText(_("Display Name"), name='displayName', value=self.data.get('displayName',''), style='green'), + self.myparent.getTitledText(_("IconURL"), name='description', value=self.data.get('description',''), style='green'), + + self.myparent.getTitledWidget( + _("Authorization Policies"), + name='umaAuthorizationPolicies', + widget=DropDownWidget( + values= self.data.get('umaAuthorizationPolicies',[]), + # value=self.data.get('tokenEndpointAuthMethodsSupported') + ), + jans_help=self.myparent.get_help_from_schema(schema, 'tokenEndpointAuthMethodsSupported'), + style='green' + ), + + self.myparent.getTitledText(_("Associated Client"), name='none', value=self.data.get('none',''), style='green',read_only=True,height=3,), ## Not fount + self.myparent.getTitledText(_("Creationg time"), name='description', value=self.data.get('description',''), style='green',read_only=True,), + self.myparent.getTitledText(_("Creator"), name='Creator', value=self.data.get('Creator',''), style='green',read_only=True,), + + ],width=D(), + ) + self.left_nav = list(self.tabs.keys())[0] + + + def scope_dialog_nav_selection_changed(self, selection): + self.left_nav = selection + + def __pt_container__(self): + return self.dialog From 5bb7b9304d9293a57441e4f156fb3648afa70d60 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Tue, 13 Sep 2022 09:23:05 -0700 Subject: [PATCH 112/364] feat:jans-cli scope tas --- jans-cli-tui/plugins/010_oxauth/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jans-cli-tui/plugins/010_oxauth/main.py b/jans-cli-tui/plugins/010_oxauth/main.py index d86b0f8d101..d2c8bc6d8fc 100755 --- a/jans-cli-tui/plugins/010_oxauth/main.py +++ b/jans-cli-tui/plugins/010_oxauth/main.py @@ -376,5 +376,5 @@ def edit_scope_dialog(self, **params): selected_line_data = params['data'] title = _("Edit Scopes") - dialog = EditScopeDialogDialog(self.app, title=title, data=selected_line_data, save_handler=self.save_client) + dialog = EditScopeDialog(self.app, title=title, data=selected_line_data, save_handler=self.save_client) self.app.show_jans_dialog(dialog) From 1b31e2dfddf8b85fcaafe799154bd6122bdcfd43 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Wed, 14 Sep 2022 19:23:20 +0300 Subject: [PATCH 113/364] fix: jans-cli dropdown set value --- .../plugins/010_oxauth/edit_client_dialog.py | 23 +++++++++++++++---- jans-cli-tui/utils.py | 1 - jans-cli-tui/wui_components/jans_drop_down.py | 4 ++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py b/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py index faf45f793ff..16c5dec0a55 100755 --- a/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py +++ b/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py @@ -73,7 +73,12 @@ def save(): if not cfr: return - self.myparent.logger.debug('handler: '+str(save_handler)) + for ditem in self.drop_down_select_first: + if ditem in self.data and self.data[ditem] is None: + self.data.pop(ditem) + + self.myparent.logger.debug('DATA: '+str(self.data)) + close_me = True if save_handler: close_me = self.save_handler(self) @@ -290,12 +295,19 @@ def prepare_tabs(self): ] + self.drop_down_select_first = [] + + + # keep this line until this issue is closed https://github.com/JanssenProject/jans/issues/2372 + self.myparent.cli_object.openid_configuration['access_token_singing_alg_values_supported'] = ['HS256', 'HS384', 'HS512', 'RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512', 'PS256', 'PS384', 'PS512'] + + for title, swagger_key, openid_key in ( (_("ID Token Alg for Signing "), 'idTokenSignedResponseAlg', 'id_token_signing_alg_values_supported'), (_("ID Token Alg for Encryption"), 'idTokenEncryptedResponseAlg', 'id_token_encryption_alg_values_supported'), (_("ID Token Enc for Encryption"), 'idTokenEncryptedResponseEnc', 'id_token_encryption_enc_values_supported'), - #(_("Access Token Alg for Signing "), 'accessTokenSigningAlg', 'id_token_signing_alg_values_supported'), #?? openid key + (_("Access Token Alg for Signing "), 'accessTokenSigningAlg', 'access_token_singing_alg_values_supported'), #?? openid key (_("User Info for Signing "), 'userInfoSignedResponseAlg', 'userinfo_signing_alg_values_supported'), (_("User Info Alg for Encryption"), 'userInfoEncryptedResponseAlg', 'userinfo_encryption_alg_values_supported'), @@ -306,17 +318,20 @@ def prepare_tabs(self): (_("Request Object Enc for Encryption"), 'requestObjectEncryptionEnc', 'request_object_encryption_enc_values_supported'), ): + self.drop_down_select_first.append(swagger_key) + + values = [ (alg, alg) for alg in self.myparent.cli_object.openid_configuration[openid_key] ] + encryption_signing.append(self.myparent.getTitledWidget( title, name=swagger_key, widget=DropDownWidget( - values=[ (alg, alg) for alg in self.myparent.cli_object.openid_configuration[openid_key] ], + values=values, value=self.data.get(swagger_key) ), jans_help=self.myparent.get_help_from_schema(schema, swagger_key), style='green')) - self.tabs['Encryption/Signing'] = HSplit(encryption_signing) def allow_spontaneous_changed(cb): diff --git a/jans-cli-tui/utils.py b/jans-cli-tui/utils.py index 1891f2c91f5..681e2f8cafe 100755 --- a/jans-cli-tui/utils.py +++ b/jans-cli-tui/utils.py @@ -31,7 +31,6 @@ def make_data_from_dialog(self): value_ = me.value data[key_] = value_ - return data diff --git a/jans-cli-tui/wui_components/jans_drop_down.py b/jans-cli-tui/wui_components/jans_drop_down.py index 72bfce0672a..addc921aa52 100755 --- a/jans-cli-tui/wui_components/jans_drop_down.py +++ b/jans-cli-tui/wui_components/jans_drop_down.py @@ -105,6 +105,8 @@ def up(self): else: self.selected_line = (self.selected_line - 1) % (self.height) + self.set_value(self.values[self.selected_line][0]) + def down(self): """_summary_ """ @@ -117,6 +119,8 @@ def down(self): else: self.selected_line = (self.selected_line + 1) % (self.height) + self.set_value(self.values[self.selected_line][0]) + def __pt_container__(self): return self.container From 5131cae9a11f191d146888151de3a8ede24e080d Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Thu, 15 Sep 2022 00:21:19 -0700 Subject: [PATCH 114/364] fix:jans-cli test uma-resources --- jans-cli-tui/wrapper_test.py | 39 ++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/jans-cli-tui/wrapper_test.py b/jans-cli-tui/wrapper_test.py index d918c5131ff..c90f9d2b913 100755 --- a/jans-cli-tui/wrapper_test.py +++ b/jans-cli-tui/wrapper_test.py @@ -63,6 +63,41 @@ """ -result = cli_object.process_command_by_id('get-oauth-scopes', '', 'limit:10', {}) +# result = cli_object.process_command_by_id( +# 'get-oauth-uma-resources', '', 'limit:10', {}) -print(result) + +result = cli_object.process_command_by_id( + operation_id='get-oauth-uma-resources', + url_suffix='', + endpoint_args='pattern:', + data_fn=None + ) +print(result.text) + + +''' +[ + {"dn":"jansId=1caf7fbe-349f-468a-ac48-8cbf24a638bd, + ou=resources, + ou=uma, + o=jans", + "id":"1caf7fbe-349f-468a-ac48-8cbf24a638bd", + "name":"test-uma-resource", + "description":"This is a test UMA Resource", + "deletable":false + }, + + {"dn":"jansId=9b993eae-0239-4d20-9c1b-bce445c5e153, + ou=resources, + ou=uma, + o=jans", + "id":"9b993eae-0239-4d20-9c1b-bce445c5e153", + "name":"test2-uma resourse", + "description":"description2", + "deletable":false + } + +] + +''' \ No newline at end of file From 13981cf69cccb5d8e42c780001e40d686746f3bc Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Thu, 15 Sep 2022 00:21:45 -0700 Subject: [PATCH 115/364] fix:jans-cli fix up and down arrows --- jans-cli-tui/wui_components/jans_drop_down.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jans-cli-tui/wui_components/jans_drop_down.py b/jans-cli-tui/wui_components/jans_drop_down.py index addc921aa52..942c4718890 100755 --- a/jans-cli-tui/wui_components/jans_drop_down.py +++ b/jans-cli-tui/wui_components/jans_drop_down.py @@ -25,7 +25,7 @@ def __init__(self, values=[], value=None, height=4, rotatable_up=True, rotatable """ self.values = values - + self.values_flag = (values[0],values[-1]) self.set_value(value) # if not self.values : @@ -98,7 +98,7 @@ def up(self): """_summary_ """ if self.selected_line == 0: - if self.rotatable_up and self.values[self.selected_line] == self.values[0]: + if self.rotatable_up and self.values[self.selected_line] == self.values_flag[0]: pass else: self.values = self.shift(self.values, -1) @@ -112,7 +112,7 @@ def down(self): """ if self.selected_line + 1 == (self.height): - if self.rotatable_down and self.values[self.selected_line] == self.values[-1]: + if self.rotatable_down and self.values[self.selected_line] == self.values_flag[-1]: pass else: self.values = self.shift(self.values, 1) From 52b19bb4efee04a1f57f86169fac6677c27b9743 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Thu, 15 Sep 2022 00:22:08 -0700 Subject: [PATCH 116/364] fix:jans-cli delete some white spaces --- jans-cli-tui/plugins/010_oxauth/main.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/jans-cli-tui/plugins/010_oxauth/main.py b/jans-cli-tui/plugins/010_oxauth/main.py index d2c8bc6d8fc..c1e774c47e4 100755 --- a/jans-cli-tui/plugins/010_oxauth/main.py +++ b/jans-cli-tui/plugins/010_oxauth/main.py @@ -269,8 +269,6 @@ def oauth_update_scopes(self, start_index=0): get_app().invalidate() - - def oauth_get_scopes(self): """Method to get the Scopes data from server """ @@ -281,18 +279,15 @@ def oauth_get_scopes(self): def display_scope(self): pass - def edit_scope(self, selected,event,size): ## enter - self.edit_scope_dialog() - def edit_client_dialog(self, **params): + selected_line_data = params['data'] title = "Edit user Data (Clients)" self.app.logger.debug("START") self.app.logger.debug(selected_line_data) self.app.logger.debug("END") - - dialog = EditClientDialog(self.app, title=title, data=selected_line_data, save_handler=self.save_client) + dialog = EditClientDialog(self.app, title=title, data=selected_line_data,save_handler=self.save_client) self.app.show_jans_dialog(dialog) def save_client(self, dialog): @@ -371,8 +366,8 @@ async def coroutine(): ensure_future(coroutine()) - def edit_scope_dialog(self, **params): + selected_line_data = params['data'] title = _("Edit Scopes") From a08f205e7f82bffe02c135415c933adb56d4c3d2 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Thu, 15 Sep 2022 00:22:38 -0700 Subject: [PATCH 117/364] fix:jans-cli add uma-resources button and seach --- .../plugins/010_oxauth/edit_client_dialog.py | 116 +++++++++++++++--- 1 file changed, 102 insertions(+), 14 deletions(-) diff --git a/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py b/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py index 16c5dec0a55..9babd09ff70 100755 --- a/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py +++ b/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py @@ -21,6 +21,7 @@ from wui_components.jans_drop_down import DropDownWidget from wui_components.jans_data_picker import DateSelectWidget from utils import DialogUtils +from wui_components.jans_vetrical_nav import JansVerticalNav from multi_lang import _ @@ -106,7 +107,6 @@ def cancel(): width=self.myparent.dialog_width, ) - def prepare_tabs(self): """Prepare the tabs for Edil Client Dialogs """ @@ -145,6 +145,9 @@ def prepare_tabs(self): current_value=self.data.get('subjectType'), jans_help=self.myparent.get_help_from_schema(schema, 'subjectType'), style='green'), + + self.myparent.getTitledText(_("Sector Identifier URI"), name='sectorIdentifierUri', value=self.data.get('sectorIdentifierUri',''), jans_help=self.myparent.get_help_from_schema(schema, 'sectorIdentifierUri'), style='green'), + self.myparent.getTitledCheckBoxList( _("Grant"), name='grantTypes', @@ -205,7 +208,8 @@ def prepare_tabs(self): value=self.data.get('additionalAudience',''), style='green'), VSplit([ - Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3) + Label(text=_("audiences"),style='bold',width=len(_("audiences")*2)), ## TODO + Button("+", handler=self.myparent.show_again,left_symbol='[',right_symbol=']',width=3) ]), self.myparent.getTitledText(_("Access token lifetime"), name='accessTokenLifetime', value=self.data.get('accessTokenLifetime',''),style='green'), self.myparent.getTitledText(_("Refresh token lifetime"), name='refreshTokenLifetime', value=self.data.get('refreshTokenLifetime',''),style='green'), @@ -230,13 +234,13 @@ def prepare_tabs(self): #self.myparent.getTitledText(title ="Logo URI", name='logoUri', value=self.data.get('logoUri',''),style='green'), #self.myparent.getTitledText(title ="Term of service URI", name='tosUri', value=self.data.get('tosUri',''),style='green'), - self.myparent.getTitledText(_("Contacts"), + self.myparent.getTitledText(_("Contacts"), ### height =3 insted of the <+> button name='contacts', value='\n'.join(self.data.get('contacts', [])), height=3, style='green'), - self.myparent.getTitledText(_("Authorized JS origins"), + self.myparent.getTitledText(_("Authorized JS origins"), ### height =3 insted of the <+> button name='authorizedOrigins', value='\n'.join(self.data.get('authorizedOrigins', [])), height=3, @@ -248,6 +252,20 @@ def prepare_tabs(self): ],width=D()) + + self.uma_resources = HSplit([],width=D()) + self.resources = HSplit([ + VSplit([ + self.myparent.getButton(text=_("Get Resources"), name='oauth:Resources:get', jans_help=_("Retreive UMA Resources"), handler=self.oauth_update_uma_resources), + self.myparent.getTitledText(_("Search"), name='oauth:Resources:search', jans_help=_("Press enter to perform search"), accept_handler=self.oauth_update_uma_resources), + + ], + padding=3, + width=D(), + ), + DynamicContainer(lambda: self.uma_resources) + ]) + self.tabs['CIBA/PAR/UMA'] = HSplit([ Label(text=_("CIBA"),style='bold'), self.myparent.getTitledRadioButton(_("Token delivery method"), name='backchannelTokenDeliveryMode', current_value=self.data.get('backchannelTokenDeliveryMode'), values=['poll','push', 'ping'],style='green'), @@ -283,7 +301,8 @@ def prepare_tabs(self): style='green'), - Label(text=_("tabel"),style='blue'), ## TODO with Jans VerticalNav + + self.resources, ] ) @@ -356,26 +375,34 @@ def allow_spontaneous_changed(cb): self.spontaneous_scopes, - #self.myparent.getTitledText("Spontaneous Scopes", - # name='spontaneousScopes', - # value='\n'.join(self.data.get('spontaneousScopes', [])), - # height=3, - # style='green'), + + VSplit([ ## TODO what the functionality would be? + Label(text=_("Spontaneous scopes"),style='bold',width=len(_("Spontaneous scopes")*2)), ## TODO + Button(_("View current"), handler=self.myparent.show_again,left_symbol='<',right_symbol='>',width=len(_("View current"))+2) + ]), self.myparent.getTitledText(_("Initial Login URI"), name='initiateLoginUri', value=self.data.get('initiateLoginUri',''),style='green'), - self.myparent.getTitledText(_("Request URIs"), + self.myparent.getTitledText(_("Request URIs"), ### height =3 insted of the <+> button name='requestUris', value='\n'.join(self.data.get('requestUris', [])), height=3, style='green'), - self.myparent.getTitledText(_("Default ACR"), + self.myparent.getTitledText(_("Default ACR"), ### height =3 >> "the type is array" cant be dropdown + name='defaultAcrValues', + value='\n'.join(self.data.get('defaultAcrValues', [])), + height=3, + style='green'), + + self.myparent.getTitledText(_("Allowed ACR"), ### height =3 insted of the <+> button name='authorizedAcrValues', value='\n'.join(self.data.get('authorizedAcrValues', [])), height=3, style='green'), - + + + self.myparent.getTitledText(_("TLS Subject DN"), name='x5c', value=self.data.get('x5c',''),style='green'), self.myparent.getTitledWidget( @@ -441,7 +468,68 @@ def allow_spontaneous_changed(cb): self.left_nav = list(self.tabs.keys())[0] - + def oauth_update_uma_resources (self, pattern=''): + """update the current uma_resources data to server + + Args: + pattern (str, optional): endpoint arguments for the uma_resources data. Defaults to ''. + """ + endpoint_args ='limit:10' + if pattern: + endpoint_args +=',pattern:'+pattern + + try : + rsponse = self.myparent.cli_object.process_command_by_id( + operation_id='get-oauth-uma-resources', + url_suffix='', + endpoint_args=endpoint_args, + data_fn=None, + data={} + ) + + except Exception as e: + self.myparent.show_message(_("Error getting clients"), str(e)) + return + + if rsponse.status_code not in (200, 201): + self.myparent.show_message(_("Error getting clients"), str(rsponse.text)) + return + + try: + result = rsponse.json() + except Exception: + self.myparent.show_message(_("Error getting clients"), str(rsponse.text)) + #press_tab + return + data =[] + + for d in result: + data.append( + [ + d['id'], + d['description'], + d.get('scopes', '') + ] + ) + + self.uma_resources = HSplit([ + JansVerticalNav( + myparent=self.myparent, + headers=['id', 'Description', 'Scopes'], + preferred_size= [0,0,0], + data=data, + on_enter=self.myparent.edit_scope_dialog, + on_display=self.myparent.data_display_dialog, + # selection_changed=self.data_selection_changed, + selectes=0, + headerColor='green', + entriesColor='blue', + all_data=result, + ), + ]) + self.myparent.logger.debug('UMA: '+str(result)) + return result + def client_dialog_nav_selection_changed(self, selection): self.left_nav = selection From 5633b3df30637f6e4c81aed98d7645d4b064c6fc Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Thu, 15 Sep 2022 00:23:22 -0700 Subject: [PATCH 118/364] fix:jans-cli remove unused imports --- .../plugins/010_oxauth/edit_scope_dialog.py | 32 +------------------ 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/jans-cli-tui/plugins/010_oxauth/edit_scope_dialog.py b/jans-cli-tui/plugins/010_oxauth/edit_scope_dialog.py index 5e7e4a25c96..a64e19ced0e 100755 --- a/jans-cli-tui/plugins/010_oxauth/edit_scope_dialog.py +++ b/jans-cli-tui/plugins/010_oxauth/edit_scope_dialog.py @@ -1,44 +1,14 @@ -import json from asyncio import Future from typing import OrderedDict - -from prompt_toolkit.widgets import Button, TextArea -from prompt_toolkit.application.current import get_app from prompt_toolkit.layout.dimension import D from static import DialogResult -from wui_components.jans_dialog import JansDialog -from prompt_toolkit.layout.containers import ( - VSplit, - DynamicContainer, -) -from prompt_toolkit.widgets import ( - Button, - Label, - TextArea, - -) from cli import config_cli from prompt_toolkit.layout.containers import ( - ConditionalContainer, - Float, HSplit, - VSplit, - VerticalAlign, DynamicContainer, - FloatContainer, - Window -) -from prompt_toolkit.widgets import ( - Box, - Button, - Frame, - Label, - RadioList, - TextArea, - CheckboxList, - Shadow, ) + from wui_components.jans_dialog_with_nav import JansDialogWithNav from wui_components.jans_side_nav_bar import JansSideNavBar from utils import DialogUtils From 2a82aea5eebd143d9f405425f11eacd8bfd7297a Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Thu, 15 Sep 2022 00:32:27 -0700 Subject: [PATCH 119/364] fix:jans-cli remove white spaces and refactor keybinding names --- jans-cli-tui/wui_components/jans_drop_down.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/jans-cli-tui/wui_components/jans_drop_down.py b/jans-cli-tui/wui_components/jans_drop_down.py index 942c4718890..2a7bcd74e09 100755 --- a/jans-cli-tui/wui_components/jans_drop_down.py +++ b/jans-cli-tui/wui_components/jans_drop_down.py @@ -27,9 +27,6 @@ def __init__(self, values=[], value=None, height=4, rotatable_up=True, rotatable self.values = values self.values_flag = (values[0],values[-1]) self.set_value(value) - - # if not self.values : - # --------------------------------------------------- # self.height = min(len(self.values), height) self.rotatable_up = rotatable_up @@ -105,12 +102,9 @@ def up(self): else: self.selected_line = (self.selected_line - 1) % (self.height) - self.set_value(self.values[self.selected_line][0]) - def down(self): """_summary_ """ - if self.selected_line + 1 == (self.height): if self.rotatable_down and self.values[self.selected_line] == self.values_flag[-1]: pass @@ -119,8 +113,6 @@ def down(self): else: self.selected_line = (self.selected_line + 1) % (self.height) - self.set_value(self.values[self.selected_line][0]) - def __pt_container__(self): return self.container @@ -201,10 +193,6 @@ def _enter(event) -> None: if self.select_box_float not in get_app().layout.container.floats: get_app().layout.container.floats.append(self.select_box_float) else: - # if self.select_box.values[self.select_box.selected_line][1] == "Select One": - # self.text = "Enter to Select" - # get_app().layout.container.floats.remove(self.select_box_float) - # else : self.text = self.select_box.values[self.select_box.selected_line][1] get_app().layout.container.floats.remove(self.select_box_float) @@ -215,7 +203,7 @@ def _up(event): self.select_box.up() @kb.add('down') - def _up(event): + def _down(event): self.select_box.down() @kb.add('escape') From 33b08b87cbe25251805e5f99be9305178ca39766 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Thu, 15 Sep 2022 00:37:20 -0700 Subject: [PATCH 120/364] fix:jans-cli revert hopa changes --- jans-cli-tui/wui_components/jans_drop_down.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/jans-cli-tui/wui_components/jans_drop_down.py b/jans-cli-tui/wui_components/jans_drop_down.py index 2a7bcd74e09..cc2366a70f5 100755 --- a/jans-cli-tui/wui_components/jans_drop_down.py +++ b/jans-cli-tui/wui_components/jans_drop_down.py @@ -102,6 +102,8 @@ def up(self): else: self.selected_line = (self.selected_line - 1) % (self.height) + self.set_value(self.values[self.selected_line][0]) + def down(self): """_summary_ """ @@ -113,6 +115,8 @@ def down(self): else: self.selected_line = (self.selected_line + 1) % (self.height) + self.set_value(self.values[self.selected_line][0]) + def __pt_container__(self): return self.container From 25c9d02df142b1d7f1dfcc6a4a20db616dfb50db Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Sat, 17 Sep 2022 05:23:02 -0700 Subject: [PATCH 121/364] fix:jans-cli try search uma-resources by client id --- jans-cli-tui/wrapper_test.py | 66 ++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/jans-cli-tui/wrapper_test.py b/jans-cli-tui/wrapper_test.py index c90f9d2b913..2f34416327d 100755 --- a/jans-cli-tui/wrapper_test.py +++ b/jans-cli-tui/wrapper_test.py @@ -67,37 +67,45 @@ # 'get-oauth-uma-resources', '', 'limit:10', {}) -result = cli_object.process_command_by_id( - operation_id='get-oauth-uma-resources', - url_suffix='', - endpoint_args='pattern:', - data_fn=None - ) +result = cli_object.process_command_by_id( + operation_id='get-oauth-uma-resources', + url_suffix='', + endpoint_args='pattern:dn', + data_fn=None, + data={} + ) print(result.text) - ''' -[ - {"dn":"jansId=1caf7fbe-349f-468a-ac48-8cbf24a638bd, - ou=resources, - ou=uma, - o=jans", - "id":"1caf7fbe-349f-468a-ac48-8cbf24a638bd", - "name":"test-uma-resource", - "description":"This is a test UMA Resource", - "deletable":false - }, - - {"dn":"jansId=9b993eae-0239-4d20-9c1b-bce445c5e153, - ou=resources, - ou=uma, - o=jans", - "id":"9b993eae-0239-4d20-9c1b-bce445c5e153", - "name":"test2-uma resourse", - "description":"description2", - "deletable":false - } - -] +{'dn': 'jansId=5635e18b-67b9-4997-a786-a6b2cdb84355,ou=resources,ou=uma,o=jans', + 'id': '5635e18b-67b9-4997-a786-a6b2cdb84355', +'name': '[GET] /document', + 'scopes': ['inum=40a48740-4892-4fce-b30f-81c5c45670f4,ou=scopes,o=jans'], + 'clients': ['inum=e218bef8-a3cb-4b01-b1c6-e3514d98fb68,ou=clients,o=jans'], + 'rev': '1', + 'creationDate': '2022-09-15T09:03:14', + 'expirationDate': '2022-10-05T09:03:14', + 'deletable': True}, + +{'dn': 'jansId=9b473b72-496b-4414-828c-a4d2bebca97b,ou=resources,ou=uma,o=jans', +'id': '9b473b72-496b-4414-828c-a4d2bebca97b', + 'name': '[GET] /photo', + 'scopes': ['inum=40a48740-4892-4fce-b30f-81c5c45670f4,ou=scopes,o=jans'], + 'clients': ['inum=e218bef8-a3cb-4b01-b1c6-e3514d98fb68,ou=clients,o=jans'], + 'rev': '1', + 'creationDate': '2022-09-15T09:03:13', + 'expirationDate': '2022-10-05T09:03:13', 'deletable': True}, + +{'dn': 'jansId=095a7fd8-e92f-43a2-a7d1-e52c123d3f02,ou=resources,ou=uma,o=jans', + 'id': '095a7fd8-e92f-43a2-a7d1-e52c123d3f02', + 'name': '[PUT, POST] /photo', + 'scopes': ['inum=514b71e9-b12b-42b5-9b9f-2453ae24ed08,ou=scopes,o=jans', + 'inum=19927467-d607-4c69-b145-f8752bd63e8c,ou=scopes,o=jans'], + 'clients': ['inum=ac1ebdf4-a794-4fef-8bcq-33291221c4a5,ou=clients,o=jans'], + 'rev': '1', + 'creationDate': '2022-09-15T09:03:14', + 'expirationDate': '2022-10-05T09:03:14', + 'deletable': True} + ] ''' \ No newline at end of file From 07bcb52d51db076f17c5e634ed753063496b35a3 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Sat, 17 Sep 2022 05:24:45 -0700 Subject: [PATCH 122/364] fix:jans-cli scripts skelton - not finished (commented for now) --- jans-cli-tui/plugins/060_scripts/main.py | 107 +++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/jans-cli-tui/plugins/060_scripts/main.py b/jans-cli-tui/plugins/060_scripts/main.py index 35f29befa99..0fc8b428ca9 100755 --- a/jans-cli-tui/plugins/060_scripts/main.py +++ b/jans-cli-tui/plugins/060_scripts/main.py @@ -28,3 +28,110 @@ def set_center_frame(self): body=HSplit([Label(text="Plugin {} is not imlemented yet".format(self.name)), Button(text="Button-{}".format(self.pid))], width=D()), height=D()) + + + +# class Plugin(): +# """This is a general class for plugins +# """ +# def __init__(self, app): +# """init for Plugin class "scripts" + +# Args: +# app (_type_): _description_ +# """ +# self.app = app +# self.pid = 'scripts' +# self.name = 'Scripts' +# self.script_containers = {} +# self.script_prepare_navbar() +# self.script_prepare_containers() +# self.script_nav_selection_changed(self.script_navbar.navbar_entries[0][0]) + +# def process(self): +# pass + +# def set_center_frame(self): +# """center frame content +# """ +# self.app.center_container = self.script_main_container + + + +# def script_prepare_navbar(self): +# """prepare the navbar for the current Plugin +# """ +# self.script_navbar = JansSideNavBar( +# entries=[('clients', 'Clients'), ('scopes', 'Scopes'), ('keys', 'Keys'), ('defaults', 'Defaults'), ('properties', 'Properties'), ('logging', 'Logging')], +# selection_changed=self.script_nav_selection_changed, +# select=0, +# # bgcolor='#66d9ef' +# ) + + +# def script_nav_selection_changed(self, selection): +# """This method for the selection change + +# Args: +# selection (str): the current selected tab +# """ +# self.app.logger.debug('OXUATH NAV: %s', selection) +# if selection in self.script_containers: +# self.script_main_area = self.script_containers[selection] +# else: +# self.script_main_area = self.app.not_implemented + + +# def oauth_get_scopes(self): +# pass +# def oauth_get_clients(self): +# pass +# def search_clients(self): +# pass +# def add_client(self): +# pass +# def script_prepare_containers(self): +# """prepare the main container (tabs) for the current Plugin +# """ + +# self.script_data_container = { +# 'clients' :HSplit([],width=D()), +# 'scopes' :HSplit([],width=D()), + +# } +# self.script_main_area = HSplit([],width=D()) + +# self.script_containers['scopes'] = HSplit([ +# VSplit([ +# self.app.getButton(text=_("Get Scopes"), name='oauth:scopes:get', jans_help=_("Retreive first 10 Scopes"), handler=self.oauth_get_scopes), +# self.app.getTitledText(_("Search: "), name='oauth:scopes:search', jans_help=_("Press enter to perform search")), +# self.app.getButton(text=_("Add Scope"), name='oauth:scopes:add', jans_help=_("To add a new scope press this button")), +# ], +# padding=3, +# width=D(), +# ), +# DynamicContainer(lambda: self.script_data_container['scopes']) +# ]) + +# self.script_containers['clients'] = HSplit([ +# VSplit([ +# self.app.getButton(text=_("Get Clients"), name='oauth:clients:get', jans_help=_("Retreive first 10 OpenID Connect clients"), handler=self.oauth_get_clients), +# self.app.getTitledText(_("Search"), name='oauth:clients:search', jans_help=_("Press enter to perform search"), accept_handler=self.search_clients), +# self.app.getButton(text=_("Add Client"), name='oauth:clients:add', jans_help=_("To add a new client press this button"), handler=self.add_client), + +# ], +# padding=3, +# width=D(), +# ), +# DynamicContainer(lambda: self.script_data_container['clients']) +# ] +# ) + +# self.script_main_container = self.script_navbar + +# # VSplit([ +# # self.script_navbar, +# # DynamicContainer(lambda: self.script_main_area), +# # ], +# # height=D(), +# # ) From acec117ee18a77ec0711025f2e34953f584a622c Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Sat, 17 Sep 2022 05:26:30 -0700 Subject: [PATCH 123/364] feat:jans-cli uma-resources search and get -tie to client (pattern is missing) --- .../plugins/010_oxauth/edit_client_dialog.py | 61 ++++++++++++++++--- 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py b/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py index 9babd09ff70..3e9e888fe6e 100755 --- a/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py +++ b/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py @@ -12,6 +12,7 @@ Button, Label, ) +from prompt_toolkit.application.current import get_app from cli import config_cli from static import DialogResult @@ -22,6 +23,8 @@ from wui_components.jans_data_picker import DateSelectWidget from utils import DialogUtils from wui_components.jans_vetrical_nav import JansVerticalNav +from view_uma_dialog import ViewUMADialog +import threading from multi_lang import _ @@ -256,8 +259,8 @@ def prepare_tabs(self): self.uma_resources = HSplit([],width=D()) self.resources = HSplit([ VSplit([ - self.myparent.getButton(text=_("Get Resources"), name='oauth:Resources:get', jans_help=_("Retreive UMA Resources"), handler=self.oauth_update_uma_resources), - self.myparent.getTitledText(_("Search"), name='oauth:Resources:search', jans_help=_("Press enter to perform search"), accept_handler=self.oauth_update_uma_resources), + self.myparent.getButton(text=_("Get Resources"), name='oauth:Resources:get', jans_help=_("Retreive UMA Resources"), handler=self.oauth_get_uma_resources), + self.myparent.getTitledText(_("Search"), name='oauth:Resources:search', jans_help=_("Press enter to perform search"), accept_handler=self.search_uma_resources), ], padding=3, @@ -468,6 +471,24 @@ def allow_spontaneous_changed(cb): self.left_nav = list(self.tabs.keys())[0] + + + def oauth_get_uma_resources(self): + """Method to get the clients data from server + """ + t = threading.Thread(target=self.oauth_update_uma_resources, daemon=True) + t.start() + + + def search_uma_resources(self, tbuffer): + if not len(tbuffer.text) > 2: + self.myparent.show_message(_("Error!"), _("Search string should be at least three characters")) + return + + t = threading.Thread(target=self.oauth_update_uma_resources, args=(tbuffer.text,), daemon=True) + t.start() + + def oauth_update_uma_resources (self, pattern=''): """update the current uma_resources data to server @@ -501,24 +522,27 @@ def oauth_update_uma_resources (self, pattern=''): self.myparent.show_message(_("Error getting clients"), str(rsponse.text)) #press_tab return + data =[] for d in result: data.append( [ - d['id'], - d['description'], - d.get('scopes', '') + str(d['id']), + str(d.get('description', '')), + str(d.get('scopes', [''])[0] ) ] ) + - self.uma_resources = HSplit([ + if data : + self.uma_resources = HSplit([ JansVerticalNav( myparent=self.myparent, headers=['id', 'Description', 'Scopes'], - preferred_size= [0,0,0], + preferred_size= [36,0,0], data=data, - on_enter=self.myparent.edit_scope_dialog, + on_enter=self.view_uma_resources, on_display=self.myparent.data_display_dialog, # selection_changed=self.data_selection_changed, selectes=0, @@ -527,12 +551,29 @@ def oauth_update_uma_resources (self, pattern=''): all_data=result, ), ]) - self.myparent.logger.debug('UMA: '+str(result)) - return result + # self.uma_result = result + + # return result + get_app().invalidate() + else: + self.myparent.show_message(_("Oops"), _("No matching result")) + + def client_dialog_nav_selection_changed(self, selection): self.left_nav = selection + def view_uma_resources(self, **params): + + selected_line_data = params['data'] ##self.uma_result + title = "Edit user Data (Clients)" + + dialog = ViewUMADialog(self.myparent, title=title, data=selected_line_data, save_handler=self.save_client) + + self.myparent.show_jans_dialog(dialog) + + def save_client(self, dialog): + pass def __pt_container__(self): return self.dialog From 8774ae4d63177d6c9c06021f7bad5038a90aaf34 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Sat, 17 Sep 2022 05:38:25 -0700 Subject: [PATCH 124/364] fix:jans-cli fix the focuse after wrong serach or less than 3 char --- .../plugins/010_oxauth/edit_scope_dialog.py | 32 ++++++++++++++++++- jans-cli-tui/plugins/010_oxauth/main.py | 5 +-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/jans-cli-tui/plugins/010_oxauth/edit_scope_dialog.py b/jans-cli-tui/plugins/010_oxauth/edit_scope_dialog.py index a64e19ced0e..5e7e4a25c96 100755 --- a/jans-cli-tui/plugins/010_oxauth/edit_scope_dialog.py +++ b/jans-cli-tui/plugins/010_oxauth/edit_scope_dialog.py @@ -1,14 +1,44 @@ +import json from asyncio import Future from typing import OrderedDict + +from prompt_toolkit.widgets import Button, TextArea +from prompt_toolkit.application.current import get_app from prompt_toolkit.layout.dimension import D from static import DialogResult +from wui_components.jans_dialog import JansDialog +from prompt_toolkit.layout.containers import ( + VSplit, + DynamicContainer, +) +from prompt_toolkit.widgets import ( + Button, + Label, + TextArea, + +) from cli import config_cli from prompt_toolkit.layout.containers import ( + ConditionalContainer, + Float, HSplit, + VSplit, + VerticalAlign, DynamicContainer, + FloatContainer, + Window +) +from prompt_toolkit.widgets import ( + Box, + Button, + Frame, + Label, + RadioList, + TextArea, + CheckboxList, + Shadow, ) - from wui_components.jans_dialog_with_nav import JansDialogWithNav from wui_components.jans_side_nav_bar import JansSideNavBar from utils import DialogUtils diff --git a/jans-cli-tui/plugins/010_oxauth/main.py b/jans-cli-tui/plugins/010_oxauth/main.py index c1e774c47e4..41257572d3e 100755 --- a/jans-cli-tui/plugins/010_oxauth/main.py +++ b/jans-cli-tui/plugins/010_oxauth/main.py @@ -199,7 +199,8 @@ def oauth_update_clients(self, pattern=''): get_app().invalidate() else: - self.app.show_message(_("Oops"), _("No matching result")) + self.app.show_message(_("Oops"), _("No matching result"),tobefocused = self.oauth_containers['clients']) + # self.app.layout.focus(self.oauth_containers['clients']) def oauth_get_clients(self): """Method to get the clients data from server @@ -320,7 +321,7 @@ def save_client(self, dialog): def search_clients(self, tbuffer): if not len(tbuffer.text) > 2: - self.app.show_message(_("Error!"), _("Search string should be at least three characters")) + self.app.show_message(_("Error!"), _("Search string should be at least three characters"),tobefocused=self.oauth_containers['clients']) return t = threading.Thread(target=self.oauth_update_clients, args=(tbuffer.text,), daemon=True) From 3a68650755785a4f9535733e09d26d9155d5f512 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Sat, 17 Sep 2022 05:39:03 -0700 Subject: [PATCH 125/364] feat:jans-cli add UMA dialog to view data or delete it --- .../plugins/010_oxauth/view_uma_dialog.py | 154 ++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 jans-cli-tui/plugins/010_oxauth/view_uma_dialog.py diff --git a/jans-cli-tui/plugins/010_oxauth/view_uma_dialog.py b/jans-cli-tui/plugins/010_oxauth/view_uma_dialog.py new file mode 100644 index 00000000000..76da9b13d1d --- /dev/null +++ b/jans-cli-tui/plugins/010_oxauth/view_uma_dialog.py @@ -0,0 +1,154 @@ +import json +from asyncio import Future +from typing import OrderedDict + +from prompt_toolkit.widgets import Button, TextArea +from prompt_toolkit.application.current import get_app +from prompt_toolkit.layout.dimension import D +from static import DialogResult +from wui_components.jans_dialog import JansDialog +from prompt_toolkit.layout.containers import ( + VSplit, + DynamicContainer, +) +from prompt_toolkit.widgets import ( + Button, + Label, + TextArea, + +) +from prompt_toolkit.widgets import ( + Button, + Dialog, + VerticalLine, +) +from cli import config_cli +from prompt_toolkit.layout.containers import ( + ConditionalContainer, + Float, + HSplit, + VSplit, + VerticalAlign, + DynamicContainer, + FloatContainer, + Window +) +from prompt_toolkit.widgets import ( + Box, + Button, + Frame, + Label, + RadioList, + TextArea, + CheckboxList, + Shadow, +) +from wui_components.jans_dialog_with_nav import JansDialogWithNav +from wui_components.jans_side_nav_bar import JansSideNavBar +from utils import DialogUtils + +from wui_components.jans_cli_dialog import JansGDialog + +from wui_components.jans_drop_down import DropDownWidget + +from multi_lang import _ + + +class ViewUMADialog(JansGDialog, DialogUtils): + + def __init__(self, parent, title, data, buttons=[], save_handler=None): + + super().__init__(parent, title, buttons) + self.save_handler = save_handler + self.data = data + + def delete(): + self.myparent.show_again() + # self.future.set_result(DialogResult.CANCEL) + + def cancel(): + self.future.set_result(DialogResult.CANCEL) + +# {"dn":"jansId=1caf7fbe-349f-468a-ac48-8cbf24a638bd, +# ou=resources, +# ou=uma, +# o=jans", +# "id":"1caf7fbe-349f-468a-ac48-8cbf24a638bd", +# "name":"test-uma-resource", +# "description":"This is a test UMA Resource", +# "deletable":false + + self.dialog = Dialog(title='title', + + body= + HSplit([ + + self.myparent.getTitledText( + "Resource id", + name='id', + value=self.data.get('id',''), + read_only=True, + style='green', + ), + + self.myparent.getTitledText( + "Display Name", + name='name', + value=self.data.get('name',''), + read_only=True, + style='green'), + + self.myparent.getTitledText( + "IconURL", + name='iconUri', + value=self.data.get('iconUri',''), + read_only=True, + style='green'), + + Label(text=_("Scope Selection"),style='bold',width=len(_("Scope Selection"))), ## TODO dont know what is that + + self.myparent.getTitledText( + "Scope or Expression", + name='scopeExpression', + value=self.data.get('scopeExpression',''), + read_only=True, + style='green', + # height=3 ### enmpty spaces eccures here + ), + + self.myparent.getTitledText( + "Associated Client", + name='clients', + value=self.data.get('clients',''), + read_only=True, + style='green'), + + self.myparent.getTitledText( + "Creation time", + name='creationDate', + value=self.data.get('creationDate',''), + read_only=True, + style='green'), + + ], padding=1,width=self.myparent.dialog_width,), + buttons=[ + Button( + text="Cancel", + handler=cancel, + ) , + Button( + text="Delete", + handler=delete, + ) + ], + with_background=False, + # width=140, + + + + ) + + + def __pt_container__(self): + return self.dialog + From 36dbc61a5d2926434b1b8d22037c843b283320e7 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Sat, 17 Sep 2022 05:40:30 -0700 Subject: [PATCH 126/364] fix:jans-cli clean and refactor --- jans-cli-tui/plugins/010_oxauth/edit_scope_dialog.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/jans-cli-tui/plugins/010_oxauth/edit_scope_dialog.py b/jans-cli-tui/plugins/010_oxauth/edit_scope_dialog.py index 5e7e4a25c96..3a5ad45440d 100755 --- a/jans-cli-tui/plugins/010_oxauth/edit_scope_dialog.py +++ b/jans-cli-tui/plugins/010_oxauth/edit_scope_dialog.py @@ -180,7 +180,6 @@ def prepare_tabs(self): ],width=D(), ) - self.tabs['Dynamic'] = HSplit([ self.myparent.getTitledText(_("id"), name='id', value=self.data.get('id',''), style='green'), self.myparent.getTitledText(_("inum"), name='inum', value=self.data.get('inum',''), style='green',read_only=True), @@ -217,7 +216,6 @@ def prepare_tabs(self): ],width=D(), ) - self.tabs['Spontaneous'] = HSplit([ self.myparent.getTitledText(_("id"), name='id', value=self.data.get('id',''), style='green',read_only=True,), self.myparent.getTitledText(_("inum"), name='inum', value=self.data.get('inum',''), style='green',read_only=True,), @@ -230,7 +228,6 @@ def prepare_tabs(self): ],width=D(), ) - self.tabs['UMA'] = HSplit([ self.myparent.getTitledText(_("id"), name='id', value=self.data.get('id',''), style='green'), self.myparent.getTitledText(_("inum"), name='inum', value=self.data.get('inum',''), style='green',read_only=True,), From d14fb3ab3ff2435b9b218937fd8c844fa4fe9696 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Sat, 17 Sep 2022 05:40:58 -0700 Subject: [PATCH 127/364] fix:jans-cli fix the focuse after wrong serach or less than 3 char --- jans-cli-tui/jans-cli-tui.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index e1713d4f50a..083230c07a0 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -522,11 +522,17 @@ def edit_scope_dialog(self, **params): # self.show_jans_dialog(dialog) pass - def show_message(self, title, message, buttons=[]): + def show_message(self, title, message, buttons=[],tobefocused=None): body = HSplit([Label(message)]) dialog = JansMessageDialog(title=title, body=body, buttons=buttons) - focused_before = self.root_layout.floats[-1].content if self.root_layout.floats else self.layout.current_window + if not tobefocused: + focused_before = self.root_layout.floats[-1].content if self.root_layout.floats else self.layout.current_window #show_message + else : + focused_before = self.root_layout.floats[-1].content if self.root_layout.floats else tobefocused + self.logger.debug("********************************") + self.logger.debug("focused_before = " + str(focused_before)) + self.logger.debug("********************************") float_ = Float(content=dialog) self.root_layout.floats.append(float_) dialog.me = float_ From c674e2be4b2acb6ee8a8e5f1631827b94190400f Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Sat, 17 Sep 2022 05:42:16 -0700 Subject: [PATCH 128/364] fix:jans-cli add parent --- jans-cli-tui/wui_components/jans_side_nav_bar.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/jans-cli-tui/wui_components/jans_side_nav_bar.py b/jans-cli-tui/wui_components/jans_side_nav_bar.py index 83946c476f2..a02d82b119b 100755 --- a/jans-cli-tui/wui_components/jans_side_nav_bar.py +++ b/jans-cli-tui/wui_components/jans_side_nav_bar.py @@ -12,7 +12,7 @@ class JansSideNavBar(): """This is a Vertical Navigation bar Widget with one value used in clients/scopes dialogs """ - def __init__(self, myparent, entries, selection_changed, select=0, entries_color='#00ff44'): + def __init__(self, entries, selection_changed, myparent=None, select=0, entries_color='#00ff44'): """init for JansSideNavBar Args: @@ -29,7 +29,8 @@ def __init__(self, myparent, entries, selection_changed, select=0, entries_color select=0, entries_color='#2600ff') """ - self.myparent = myparent # ListBox parent class + if myparent : + self.myparent = myparent # ListBox parent class self.navbar_entries = entries # ListBox entries self.cur_navbar_selection = select # ListBox initial selection self.entries_color = entries_color From 5d0ca07facc7588800402f3160a43c0e862bb558 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Sat, 17 Sep 2022 11:38:24 -0700 Subject: [PATCH 129/364] fix:jans-cli remove white spaces and depuging lines --- jans-cli-tui/plugins/010_oxauth/main.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/jans-cli-tui/plugins/010_oxauth/main.py b/jans-cli-tui/plugins/010_oxauth/main.py index 41257572d3e..d5f5c81b498 100755 --- a/jans-cli-tui/plugins/010_oxauth/main.py +++ b/jans-cli-tui/plugins/010_oxauth/main.py @@ -285,9 +285,6 @@ def edit_client_dialog(self, **params): selected_line_data = params['data'] title = "Edit user Data (Clients)" - self.app.logger.debug("START") - self.app.logger.debug(selected_line_data) - self.app.logger.debug("END") dialog = EditClientDialog(self.app, title=title, data=selected_line_data,save_handler=self.save_client) self.app.show_jans_dialog(dialog) From 16b3e279602753e0fe3b381aace20f298568753f Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Sat, 17 Sep 2022 11:39:29 -0700 Subject: [PATCH 130/364] fix:jans-cli add Scope (or Expression) in clients/UMA --- .../plugins/010_oxauth/view_uma_dialog.py | 95 ++++++++++++++----- 1 file changed, 71 insertions(+), 24 deletions(-) diff --git a/jans-cli-tui/plugins/010_oxauth/view_uma_dialog.py b/jans-cli-tui/plugins/010_oxauth/view_uma_dialog.py index 76da9b13d1d..661a5656ac2 100644 --- a/jans-cli-tui/plugins/010_oxauth/view_uma_dialog.py +++ b/jans-cli-tui/plugins/010_oxauth/view_uma_dialog.py @@ -11,6 +11,8 @@ VSplit, DynamicContainer, ) +from prompt_toolkit.key_binding import KeyBindings + from prompt_toolkit.widgets import ( Button, Label, @@ -44,6 +46,7 @@ Shadow, ) from wui_components.jans_dialog_with_nav import JansDialogWithNav +from wui_components.jans_nav_bar import JansNavBar from wui_components.jans_side_nav_bar import JansSideNavBar from utils import DialogUtils @@ -61,6 +64,9 @@ def __init__(self, parent, title, data, buttons=[], save_handler=None): super().__init__(parent, title, buttons) self.save_handler = save_handler self.data = data + self.UMA_containers = {} + self.UMA_prepare_containers() + def delete(): self.myparent.show_again() @@ -69,17 +75,17 @@ def delete(): def cancel(): self.future.set_result(DialogResult.CANCEL) -# {"dn":"jansId=1caf7fbe-349f-468a-ac48-8cbf24a638bd, -# ou=resources, -# ou=uma, -# o=jans", -# "id":"1caf7fbe-349f-468a-ac48-8cbf24a638bd", -# "name":"test-uma-resource", -# "description":"This is a test UMA Resource", -# "deletable":false + self.side_nav_bar = JansNavBar( + self, + entries=[('scope', 'scope'), ('expression', 'scope expression'), ], + selection_changed=self.oauth_nav_selection_changed, + select=0, + bgcolor='#8a8a8a' + ) + + + self.dialog = Dialog(title='UMA-resources', - self.dialog = Dialog(title='title', - body= HSplit([ @@ -105,17 +111,16 @@ def cancel(): read_only=True, style='green'), - Label(text=_("Scope Selection"),style='bold',width=len(_("Scope Selection"))), ## TODO dont know what is that - self.myparent.getTitledText( - "Scope or Expression", - name='scopeExpression', - value=self.data.get('scopeExpression',''), - read_only=True, - style='green', - # height=3 ### enmpty spaces eccures here - ), + VSplit([ + Label(text=_("Scope Selection"),style='green bold',width=len(_("Scope Selection"))), ## TODO dont know what is that + + Box(self.side_nav_bar.nav_window, style='fg:#4D4D4D bg:#ffffff', height=1), + ]), + + DynamicContainer(lambda: self.oauth_main_area), + self.myparent.getTitledText( "Associated Client", name='clients', @@ -130,7 +135,9 @@ def cancel(): read_only=True, style='green'), - ], padding=1,width=self.myparent.dialog_width,), + ], padding=1,width=100, + # key_bindings=self.get_uma_dialog_key_bindings() + ), buttons=[ Button( text="Cancel", @@ -143,11 +150,51 @@ def cancel(): ], with_background=False, # width=140, - - - ) - + + + def UMA_prepare_containers(self): + + self.oauth_main_area = self.UMA_containers['scope'] = HSplit([ + self.myparent.getTitledText( + "Scopes", + name='scope', + value=self.data.get('scope',[]), + read_only=True, + style='green', + ) + + ],width=D()) + + + + self.UMA_containers['expression'] = HSplit([ + self.myparent.getTitledText( + "Expression", + name='expression', + value=self.data.get('expression',[]), + read_only=True, + style='green', + ), + + ],width=D()) + + + + + def oauth_nav_selection_changed(self, selection): + """This method for the selection change + + Args: + selection (str): the current selected tab + """ + if selection in self.UMA_containers: + self.oauth_main_area = self.UMA_containers[selection] + + + + def save(self): + pass def __pt_container__(self): return self.dialog From 46e1ed15d227ac5cf6c664fab5371a4db0d2d568 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Sat, 17 Sep 2022 11:50:08 -0700 Subject: [PATCH 131/364] fix:jans-cli add umaAuthorizationPolicies insted of (Claims Gathering Script-RPT Mofification Script) --- .../plugins/010_oxauth/edit_client_dialog.py | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py b/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py index 3e9e888fe6e..efdba45386d 100755 --- a/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py +++ b/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py @@ -291,20 +291,25 @@ def prepare_tabs(self): self.myparent.getTitledText(title =_("Claims redirect URI"), name='claimRedirectUris', value=self.data.get('claimRedirectUris',''),style='green'), - self.myparent.getTitledText(_("RPT Mofification Script"), - name='rptClaimsScripts', - value='\n'.join(self.data.get('rptClaimsScripts', [])), + # self.myparent.getTitledText(_("RPT Mofification Script"), + # name='rptClaimsScripts', + # value='\n'.join(self.data.get('rptClaimsScripts', [])), + # height=3, + # style='green'), + + # self.myparent.getTitledText(_("Claims Gathering Script"), + # name='claimRedirectUris', + # value='\n'.join(self.data.get('claimRedirectUris', [])), + # height=3, + # style='green'), + + + self.myparent.getTitledText(_("UMA Authorization Policies"), + name='umaAuthorizationPolicies', + value='\n'.join(self.data.get('umaAuthorizationPolicies', [])), height=3, style='green'), - - self.myparent.getTitledText(_("Claims Gathering Script"), - name='claimRedirectUris', - value='\n'.join(self.data.get('claimRedirectUris', [])), - height=3, - style='green'), - - - + self.resources, ] From 0a1f20b6ae9c40a6b5538a1ffd8f41dc79646ca9 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Sun, 18 Sep 2022 13:03:59 -0700 Subject: [PATCH 132/364] fix:jans-cli uncomment self.get_data_width() --- jans-cli-tui/wui_components/jans_side_nav_bar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jans-cli-tui/wui_components/jans_side_nav_bar.py b/jans-cli-tui/wui_components/jans_side_nav_bar.py index a02d82b119b..137e74327b1 100755 --- a/jans-cli-tui/wui_components/jans_side_nav_bar.py +++ b/jans-cli-tui/wui_components/jans_side_nav_bar.py @@ -57,7 +57,7 @@ def create_window(self): height=Dimension(preferred=len( self.navbar_entries)*2, max=len(self.navbar_entries)*2+1), cursorline=True, - width= 10 #self.get_data_width() + width= self.get_data_width() ), ] ), floats=[] From cec9f0ddaf01b0ae1056451084605be291f3730d Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Sun, 18 Sep 2022 13:04:42 -0700 Subject: [PATCH 133/364] feat:jans-cli add side navbar and main content for Person Authentication --- jans-cli-tui/plugins/060_scripts/main.py | 273 +++++++++++++++-------- 1 file changed, 176 insertions(+), 97 deletions(-) diff --git a/jans-cli-tui/plugins/060_scripts/main.py b/jans-cli-tui/plugins/060_scripts/main.py index 0fc8b428ca9..a10d2b5bd99 100755 --- a/jans-cli-tui/plugins/060_scripts/main.py +++ b/jans-cli-tui/plugins/060_scripts/main.py @@ -1,34 +1,19 @@ import os import sys +from prompt_toolkit.layout.containers import ( + HSplit, + VSplit, + DynamicContainer, + Window +) from prompt_toolkit.layout.containers import HSplit from prompt_toolkit.layout.dimension import D from prompt_toolkit.widgets import Button, Label, Frame -class Plugin(): - """This is a general class for plugins - """ - def __init__(self, app): - """init for Plugin class "scripts" - - Args: - app (_type_): _description_ - """ - self.app = app - self.pid = 'scripts' - self.name = 'Scripts' - - def process(self): - pass - - def set_center_frame(self): - """center frame content - """ - self.app.center_container = Frame( - body=HSplit([Label(text="Plugin {} is not imlemented yet".format(self.name)), Button(text="Button-{}".format(self.pid))], width=D()), - height=D()) - +from wui_components.jans_side_nav_bar import JansSideNavBar +from multi_lang import _ # class Plugin(): @@ -43,10 +28,6 @@ def set_center_frame(self): # self.app = app # self.pid = 'scripts' # self.name = 'Scripts' -# self.script_containers = {} -# self.script_prepare_navbar() -# self.script_prepare_containers() -# self.script_nav_selection_changed(self.script_navbar.navbar_entries[0][0]) # def process(self): # pass @@ -54,84 +35,182 @@ def set_center_frame(self): # def set_center_frame(self): # """center frame content # """ -# self.app.center_container = self.script_main_container +# self.app.center_container = Frame( +# body=HSplit([Label(text="Plugin {} is not imlemented yet".format(self.name)), Button(text="Button-{}".format(self.pid))], width=D()), +# height=D()) -# def script_prepare_navbar(self): -# """prepare the navbar for the current Plugin -# """ -# self.script_navbar = JansSideNavBar( -# entries=[('clients', 'Clients'), ('scopes', 'Scopes'), ('keys', 'Keys'), ('defaults', 'Defaults'), ('properties', 'Properties'), ('logging', 'Logging')], -# selection_changed=self.script_nav_selection_changed, -# select=0, -# # bgcolor='#66d9ef' -# ) +class Plugin(): + """This is a general class for plugins + """ + def __init__(self, app): + """init for Plugin class "scripts" -# def script_nav_selection_changed(self, selection): -# """This method for the selection change + Args: + app (_type_): _description_ + """ + self.app = app + self.pid = 'scripts' + self.name = 'Scripts' -# Args: -# selection (str): the current selected tab -# """ -# self.app.logger.debug('OXUATH NAV: %s', selection) -# if selection in self.script_containers: -# self.script_main_area = self.script_containers[selection] -# else: -# self.script_main_area = self.app.not_implemented + self.data = {} + self.script_containers = {} + self.script_prepare_navbar() + self.script_prepare_containers() + self.script_nav_selection_changed(self.script_navbar.navbar_entries[0][0]) + def process(self): + pass -# def oauth_get_scopes(self): -# pass -# def oauth_get_clients(self): -# pass -# def search_clients(self): -# pass -# def add_client(self): -# pass -# def script_prepare_containers(self): -# """prepare the main container (tabs) for the current Plugin -# """ + def set_center_frame(self): + """center frame content + """ + self.app.center_container = self.script_main_container + + + + def script_prepare_navbar(self): + """prepare the navbar for the current Plugin + """ + self.script_navbar = JansSideNavBar( + entries=['Person Authentication', 'Consent Gathering', + 'Post Authentication', 'id_token', 'Password Grant', + 'CIBA End User Notification', 'OpenID Configuration', + 'Dynamic Scope', 'Spontaneous Scope', 'Application Session', + 'End Session', 'Client Registration', 'Introspection', + 'Update Token'], + selection_changed=self.script_nav_selection_changed, + select=0, -# self.script_data_container = { -# 'clients' :HSplit([],width=D()), -# 'scopes' :HSplit([],width=D()), - -# } -# self.script_main_area = HSplit([],width=D()) - -# self.script_containers['scopes'] = HSplit([ -# VSplit([ -# self.app.getButton(text=_("Get Scopes"), name='oauth:scopes:get', jans_help=_("Retreive first 10 Scopes"), handler=self.oauth_get_scopes), -# self.app.getTitledText(_("Search: "), name='oauth:scopes:search', jans_help=_("Press enter to perform search")), -# self.app.getButton(text=_("Add Scope"), name='oauth:scopes:add', jans_help=_("To add a new scope press this button")), -# ], -# padding=3, -# width=D(), -# ), -# DynamicContainer(lambda: self.script_data_container['scopes']) -# ]) - -# self.script_containers['clients'] = HSplit([ -# VSplit([ -# self.app.getButton(text=_("Get Clients"), name='oauth:clients:get', jans_help=_("Retreive first 10 OpenID Connect clients"), handler=self.oauth_get_clients), -# self.app.getTitledText(_("Search"), name='oauth:clients:search', jans_help=_("Press enter to perform search"), accept_handler=self.search_clients), -# self.app.getButton(text=_("Add Client"), name='oauth:clients:add', jans_help=_("To add a new client press this button"), handler=self.add_client), + # bgcolor='#66d9ef' + ) + + + def script_nav_selection_changed(self, selection): + """This method for the selection change + + Args: + selection (str): the current selected tab + """ + self.app.logger.debug('OXUATH NAV: %s', selection) + if selection in self.script_containers: + self.script_main_area = self.script_containers[selection] + else: + self.script_main_area = self.not_implemented + + + def oauth_get_scopes(self): + pass + def oauth_get_clients(self): + pass + def search_clients(self): + pass + def add_client(self): + pass + def script_prepare_containers(self): + """prepare the main container (tabs) for the current Plugin + """ + + self.script_data_container = { + 'clients' :HSplit([],width=D()), + 'scopes' :HSplit([],width=D()), + + } + self.script_main_area = HSplit([],width=D()) + + self.not_implemented = Frame( + body=HSplit([Label(text=_("Not imlemented yet")), Button(text=_("MyButton"))], width=D()), + height=D(),) + + self.script_containers['Person Authentication'] = Frame( + body=HSplit([ + self.app.getTitledCheckBox(_("Enabled"), name='enabled', checked= not self.data.get('enabled'), style='green'), + self.app.getTitledText(_("Name"), name='name', value=self.data.get('name',''), style='green'), + self.app.getTitledText(_("Description"), name='description', value=self.data.get('description',''), style='green'), + VSplit([ + Label(text=_("Location"),style='green',width=len(_("Location"))), + HSplit([ + self.app.getTitledCheckBox(_("Database"), name='database', checked= not self.data.get('database'), style='green'), + VSplit([ + self.app.getTitledCheckBox(_("File System"), name='filesystem', checked= not self.data.get('filesystem'), style='green'), + self.app.getTitledText(_(""), name='filesystem', value=self.data.get('filesystem',''), style='green'), + ]), + ]), + ], + padding=3, + width=D(), + ), + Label(text=_("Level"),style='blue',width=len(_("Level"))), + Label(text=_("Properties"),style='blue',width=len(_("Properties"))), + Label(text=_("Script"),style='blue',width=len(_("Script"))), + # DynamicContainer(lambda: self.script_data_container['scopes']) + ],height=D(),width=D(),), + + height=D()) + + + HSplit([ + self.app.getTitledCheckBox(_("Enabled"), name='enabled', checked= not self.data.get('enabled'), style='green'), + self.app.getTitledText(_("Name"), name='name', value=self.data.get('name',''), style='green'), + self.app.getTitledText(_("Description"), name='description', value=self.data.get('description',''), style='green'), + + VSplit([ + Label(text=_("Location"),style='bold',width=len(_("Location"))), + HSplit([ + self.app.getTitledCheckBox(_("Database"), name='database', checked= not self.data.get('database'), style='green'), + + VSplit([ + self.app.getTitledCheckBox(_("File System"), name='filesystem', checked= not self.data.get('filesystem'), style='green'), + self.app.getTitledText(_(""), name='filesystem', value=self.data.get('filesystem',''), style='green'), + + + ]), + ]), + + Label(text=_("Level"),style='bold',width=len(_("Level"))), + Label(text=_("Properties"),style='bold',width=len(_("Properties"))), + Label(text=_("Script"),style='bold',width=len(_("Script"))), + + ], + padding=3, + width=D(), + ), + + DynamicContainer(lambda: self.script_data_container['scopes']) + ]) + + self.script_containers['Consent Gathering'] = HSplit([ + VSplit([ + self.app.getButton(text=_("Get Clients"), name='oauth:clients:get', jans_help=_("Retreive first 10 OpenID Connect clients"), handler=self.oauth_get_clients), + self.app.getTitledText(_("Search"), name='oauth:clients:search', jans_help=_("Press enter to perform search"), accept_handler=self.search_clients), + self.app.getButton(text=_("Add Client"), name='oauth:clients:add', jans_help=_("To add a new client press this button"), handler=self.add_client), -# ], -# padding=3, -# width=D(), -# ), -# DynamicContainer(lambda: self.script_data_container['clients']) -# ] -# ) - -# self.script_main_container = self.script_navbar + ], + padding=3, + width=D(), + ), + DynamicContainer(lambda: self.script_data_container['clients']) + ], + + ) + + self.script_main_container = Frame( + body=HSplit([ + VSplit([ + self.script_navbar, + DynamicContainer(lambda: self.script_main_area), + ], + height=D(), + ) + + + ], ), + height=D()) + + + + -# # VSplit([ -# # self.script_navbar, -# # DynamicContainer(lambda: self.script_main_area), -# # ], -# # height=D(), -# # ) + \ No newline at end of file From 6e5ee302ff711149d058b2f6feba4008b3e1e6ca Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Mon, 19 Sep 2022 01:20:02 -0700 Subject: [PATCH 134/364] fix:jans-cli add multilang --- .../plugins/010_oxauth/edit_client_dialog.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py b/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py index efdba45386d..da69bc96a16 100755 --- a/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py +++ b/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py @@ -120,7 +120,7 @@ def prepare_tabs(self): self.tabs['Basic'] = HSplit([ self.myparent.getTitledText( - "Client_ID", + _("Client_ID"), name='inum', value=self.data.get('inum',''), jans_help=self.myparent.get_help_from_schema(schema, 'inum'), @@ -232,10 +232,10 @@ def prepare_tabs(self): ) self.tabs['SoftwareInfo'] = HSplit([ - #self.myparent.getTitledText(title ="Client URI", name='clientUri', value=self.data.get('clientUri',''),style='green'), - #self.myparent.getTitledText(title ="Policy URI", name='policyUri', value=self.data.get('policyUri',''),style='green'), - #self.myparent.getTitledText(title ="Logo URI", name='logoUri', value=self.data.get('logoUri',''),style='green'), - #self.myparent.getTitledText(title ="Term of service URI", name='tosUri', value=self.data.get('tosUri',''),style='green'), + #self.myparent.getTitledText(title =_("Client URI"), name='clientUri', value=self.data.get('clientUri',''),style='green'), + #self.myparent.getTitledText(title =_("Policy URI"), name='policyUri', value=self.data.get('policyUri',''),style='green'), + #self.myparent.getTitledText(title =_("Logo URI"), name='logoUri', value=self.data.get('logoUri',''),style='green'), + #self.myparent.getTitledText(title =_("Term of service URI"), name='tosUri', value=self.data.get('tosUri',''),style='green'), self.myparent.getTitledText(_("Contacts"), ### height =3 insted of the <+> button name='contacts', @@ -417,7 +417,7 @@ def allow_spontaneous_changed(cb): _("Client Expiration Date"), name='expirationDate', widget=DateSelectWidget( - value=self.data.get('expirationDate', ""),parent=self + value=self.data.get('expirationDate', ''),parent=self ), jans_help=self.myparent.get_help_from_schema(schema, 'expirationDate'), style='green' @@ -571,7 +571,7 @@ def client_dialog_nav_selection_changed(self, selection): def view_uma_resources(self, **params): selected_line_data = params['data'] ##self.uma_result - title = "Edit user Data (Clients)" + title = _("Edit user Data (Clients)") dialog = ViewUMADialog(self.myparent, title=title, data=selected_line_data, save_handler=self.save_client) From fbe6112278a93df0a67dbd77c5e6cb0eace9f42f Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Mon, 19 Sep 2022 01:23:43 -0700 Subject: [PATCH 135/364] fix:jans-cli add search button in scopes/openid/claims --- jans-cli-tui/plugins/010_oxauth/edit_scope_dialog.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/jans-cli-tui/plugins/010_oxauth/edit_scope_dialog.py b/jans-cli-tui/plugins/010_oxauth/edit_scope_dialog.py index 3a5ad45440d..cdf706f49d5 100755 --- a/jans-cli-tui/plugins/010_oxauth/edit_scope_dialog.py +++ b/jans-cli-tui/plugins/010_oxauth/edit_scope_dialog.py @@ -169,11 +169,18 @@ def prepare_tabs(self): checked=self.data.get('showInConfigurationEndpoint'), style='green'), - self.myparent.getTitledText(_("Claims"), + VSplit([ + + self.myparent.getTitledText(_("Claims"), name='claims', value='\n'.join(self.data.get('claims', [])), height=3, style='green'), + self.myparent.getTitledText(_("Search"), name='oauth:scopes:openID:claims:search',style='fg:green',width=10,jans_help=_("Press enter to perform search"), ),#accept_handler=self.search_clients + + ]), + + # Label(text=_("Claims"),style='red'), ## name = claims TODO From 9b1de7443b032e10cea1119337177b957b147d66 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Mon, 19 Sep 2022 01:28:20 -0700 Subject: [PATCH 136/364] fix:jans-cli add multilang support --- jans-cli-tui/plugins/010_oxauth/main.py | 6 +++--- .../plugins/010_oxauth/view_uma_dialog.py | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/jans-cli-tui/plugins/010_oxauth/main.py b/jans-cli-tui/plugins/010_oxauth/main.py index d5f5c81b498..67d17d6bdf9 100755 --- a/jans-cli-tui/plugins/010_oxauth/main.py +++ b/jans-cli-tui/plugins/010_oxauth/main.py @@ -258,9 +258,9 @@ def oauth_update_scopes(self, start_index=0): buttons = [] if start_index > 0: - buttons.append(Button("Prev")) + buttons.append(Button(_("Prev"))) if len(result) >= 10: - buttons.append(Button("Next")) + buttons.append(Button(_("Next"))) self.app.layout.focus(scopes) # clients.focuse..!? TODO >> DONE self.oauth_data_container['scopes'] = HSplit([ @@ -283,7 +283,7 @@ def display_scope(self): def edit_client_dialog(self, **params): selected_line_data = params['data'] - title = "Edit user Data (Clients)" + title = _("Edit user Data (Clients)") dialog = EditClientDialog(self.app, title=title, data=selected_line_data,save_handler=self.save_client) self.app.show_jans_dialog(dialog) diff --git a/jans-cli-tui/plugins/010_oxauth/view_uma_dialog.py b/jans-cli-tui/plugins/010_oxauth/view_uma_dialog.py index 661a5656ac2..f5297c77db1 100644 --- a/jans-cli-tui/plugins/010_oxauth/view_uma_dialog.py +++ b/jans-cli-tui/plugins/010_oxauth/view_uma_dialog.py @@ -90,7 +90,7 @@ def cancel(): HSplit([ self.myparent.getTitledText( - "Resource id", + _("Resource id"), name='id', value=self.data.get('id',''), read_only=True, @@ -98,14 +98,14 @@ def cancel(): ), self.myparent.getTitledText( - "Display Name", + _("Display Name"), name='name', value=self.data.get('name',''), read_only=True, style='green'), self.myparent.getTitledText( - "IconURL", + _("IconURL"), name='iconUri', value=self.data.get('iconUri',''), read_only=True, @@ -122,14 +122,14 @@ def cancel(): DynamicContainer(lambda: self.oauth_main_area), self.myparent.getTitledText( - "Associated Client", + _("Associated Client"), name='clients', value=self.data.get('clients',''), read_only=True, style='green'), self.myparent.getTitledText( - "Creation time", + _("Creation time"), name='creationDate', value=self.data.get('creationDate',''), read_only=True, @@ -140,11 +140,11 @@ def cancel(): ), buttons=[ Button( - text="Cancel", + text=_("Cancel"), handler=cancel, ) , Button( - text="Delete", + text=_("Delete"), handler=delete, ) ], @@ -157,7 +157,7 @@ def UMA_prepare_containers(self): self.oauth_main_area = self.UMA_containers['scope'] = HSplit([ self.myparent.getTitledText( - "Scopes", + _("Scopes"), name='scope', value=self.data.get('scope',[]), read_only=True, @@ -170,7 +170,7 @@ def UMA_prepare_containers(self): self.UMA_containers['expression'] = HSplit([ self.myparent.getTitledText( - "Expression", + _("Expression"), name='expression', value=self.data.get('expression',[]), read_only=True, From ab538260ee10aa49e5392c10261c0d5dfc5bb377 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Mon, 19 Sep 2022 01:33:50 -0700 Subject: [PATCH 137/364] fix:jans-cli clean code and refactor --- jans-cli-tui/plugins/060_scripts/main.py | 164 +++++++---------------- 1 file changed, 50 insertions(+), 114 deletions(-) diff --git a/jans-cli-tui/plugins/060_scripts/main.py b/jans-cli-tui/plugins/060_scripts/main.py index a10d2b5bd99..8b988467b20 100755 --- a/jans-cli-tui/plugins/060_scripts/main.py +++ b/jans-cli-tui/plugins/060_scripts/main.py @@ -16,32 +16,6 @@ from multi_lang import _ -# class Plugin(): -# """This is a general class for plugins -# """ -# def __init__(self, app): -# """init for Plugin class "scripts" - -# Args: -# app (_type_): _description_ -# """ -# self.app = app -# self.pid = 'scripts' -# self.name = 'Scripts' - -# def process(self): -# pass - -# def set_center_frame(self): -# """center frame content -# """ -# self.app.center_container = Frame( -# body=HSplit([Label(text="Plugin {} is not imlemented yet".format(self.name)), Button(text="Button-{}".format(self.pid))], width=D()), -# height=D()) - - - - class Plugin(): """This is a general class for plugins """ @@ -69,8 +43,6 @@ def set_center_frame(self): """ self.app.center_container = self.script_main_container - - def script_prepare_navbar(self): """prepare the navbar for the current Plugin """ @@ -83,11 +55,8 @@ def script_prepare_navbar(self): 'Update Token'], selection_changed=self.script_nav_selection_changed, select=0, - - # bgcolor='#66d9ef' ) - def script_nav_selection_changed(self, selection): """This method for the selection change @@ -100,19 +69,9 @@ def script_nav_selection_changed(self, selection): else: self.script_main_area = self.not_implemented - - def oauth_get_scopes(self): - pass - def oauth_get_clients(self): - pass - def search_clients(self): - pass - def add_client(self): - pass def script_prepare_containers(self): """prepare the main container (tabs) for the current Plugin """ - self.script_data_container = { 'clients' :HSplit([],width=D()), 'scopes' :HSplit([],width=D()), @@ -125,89 +84,66 @@ def script_prepare_containers(self): height=D(),) self.script_containers['Person Authentication'] = Frame( - body=HSplit([ - self.app.getTitledCheckBox(_("Enabled"), name='enabled', checked= not self.data.get('enabled'), style='green'), - self.app.getTitledText(_("Name"), name='name', value=self.data.get('name',''), style='green'), - self.app.getTitledText(_("Description"), name='description', value=self.data.get('description',''), style='green'), + body=HSplit([ + self.app.getTitledCheckBox(_("Enabled"), name='enabled', checked= not self.data.get('enabled'), style='green'), + self.app.getTitledText(_("Name"), name='name', value=self.data.get('name',''), style='green'), + self.app.getTitledText(_("Description"), name='description', value=self.data.get('description',''), style='green'), + VSplit([ + Label(text=_("Location"),style='green',width=len(_("Location"))), + HSplit([ + self.app.getTitledCheckBox(_("Database"), name='database', checked= not self.data.get('database'), style='green'), VSplit([ - Label(text=_("Location"),style='green',width=len(_("Location"))), - HSplit([ - self.app.getTitledCheckBox(_("Database"), name='database', checked= not self.data.get('database'), style='green'), - VSplit([ - self.app.getTitledCheckBox(_("File System"), name='filesystem', checked= not self.data.get('filesystem'), style='green'), - self.app.getTitledText(_(""), name='filesystem', value=self.data.get('filesystem',''), style='green'), - ]), - ]), - ], - padding=3, - width=D(), - ), - Label(text=_("Level"),style='blue',width=len(_("Level"))), - Label(text=_("Properties"),style='blue',width=len(_("Properties"))), - Label(text=_("Script"),style='blue',width=len(_("Script"))), - # DynamicContainer(lambda: self.script_data_container['scopes']) - ],height=D(),width=D(),), - - height=D()) - + self.app.getTitledCheckBox(_("File System"), name='filesystem', checked= not self.data.get('filesystem'), style='green'), + self.app.getTitledText(_(""), name='filesystem', value=self.data.get('filesystem',''), style='green'), + ]), + ]), + ], + padding=3, + width=D(), + ), + Label(text=_("Level"),style='blue',width=len(_("Level"))), + Label(text=_("Properties"),style='blue',width=len(_("Properties"))), + Label(text=_("Script"),style='blue',width=len(_("Script"))), + ],height=D(),width=D(),), + + height=D()) HSplit([ - self.app.getTitledCheckBox(_("Enabled"), name='enabled', checked= not self.data.get('enabled'), style='green'), - self.app.getTitledText(_("Name"), name='name', value=self.data.get('name',''), style='green'), - self.app.getTitledText(_("Description"), name='description', value=self.data.get('description',''), style='green'), - + self.app.getTitledCheckBox(_("Enabled"), name='enabled', checked= not self.data.get('enabled'), style='green'), + self.app.getTitledText(_("Name"), name='name', value=self.data.get('name',''), style='green'), + self.app.getTitledText(_("Description"), name='description', value=self.data.get('description',''), style='green'), + VSplit([ + Label(text=_("Location"),style='bold',width=len(_("Location"))), + HSplit([ + self.app.getTitledCheckBox(_("Database"), name='database', checked= not self.data.get('database'), style='green'), VSplit([ - Label(text=_("Location"),style='bold',width=len(_("Location"))), - HSplit([ - self.app.getTitledCheckBox(_("Database"), name='database', checked= not self.data.get('database'), style='green'), - - VSplit([ - self.app.getTitledCheckBox(_("File System"), name='filesystem', checked= not self.data.get('filesystem'), style='green'), - self.app.getTitledText(_(""), name='filesystem', value=self.data.get('filesystem',''), style='green'), - - - ]), + self.app.getTitledCheckBox(_("File System"), name='filesystem', checked= not self.data.get('filesystem'), style='green'), + self.app.getTitledText(_(""), name='filesystem', value=self.data.get('filesystem',''), style='green'), ]), + ]), + Label(text=_("Level"),style='bold',width=len(_("Level"))), + Label(text=_("Properties"),style='bold',width=len(_("Properties"))), + Label(text=_("Script"),style='bold',width=len(_("Script"))), + ], + padding=3, + width=D(), + ), + DynamicContainer(lambda: self.script_data_container['scopes']) + ]) - Label(text=_("Level"),style='bold',width=len(_("Level"))), - Label(text=_("Properties"),style='bold',width=len(_("Properties"))), - Label(text=_("Script"),style='bold',width=len(_("Script"))), - - ], - padding=3, - width=D(), - ), - - DynamicContainer(lambda: self.script_data_container['scopes']) - ]) - self.script_containers['Consent Gathering'] = HSplit([ + self.script_main_container = Frame( + body=HSplit([ VSplit([ - self.app.getButton(text=_("Get Clients"), name='oauth:clients:get', jans_help=_("Retreive first 10 OpenID Connect clients"), handler=self.oauth_get_clients), - self.app.getTitledText(_("Search"), name='oauth:clients:search', jans_help=_("Press enter to perform search"), accept_handler=self.search_clients), - self.app.getButton(text=_("Add Client"), name='oauth:clients:add', jans_help=_("To add a new client press this button"), handler=self.add_client), - + self.script_navbar, + DynamicContainer(lambda: self.script_main_area), ], - padding=3, - width=D(), - ), - DynamicContainer(lambda: self.script_data_container['clients']) - ], - + height=D(), ) - - self.script_main_container = Frame( - body=HSplit([ - VSplit([ - self.script_navbar, - DynamicContainer(lambda: self.script_main_area), - ], - height=D(), - ) - - - ], ), - height=D()) + + + ], ), + height=D()) From 508539c7e8580e07eb8a5d76bc0e0515abb54630 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Mon, 19 Sep 2022 02:18:53 -0700 Subject: [PATCH 138/364] fix:jans-cli add width to getTitledText --- jans-cli-tui/jans-cli-tui.py | 1 + 1 file changed, 1 insertion(+) diff --git a/jans-cli-tui/jans-cli-tui.py b/jans-cli-tui/jans-cli-tui.py index 083230c07a0..f9c1392bdbe 100755 --- a/jans-cli-tui/jans-cli-tui.py +++ b/jans-cli-tui/jans-cli-tui.py @@ -341,6 +341,7 @@ def getTitledText(self, title, name, value='', height=1, jans_help='', accept_ha text=str(value), multiline=height > 1, height=height, + width=width, read_only=read_only, style=self.styles['textarea-readonly'] if read_only else self.styles['textarea'], accept_handler=accept_handler, From 68d366b11ef6cff0577318894a19208ac706d653 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Mon, 19 Sep 2022 02:40:07 -0700 Subject: [PATCH 139/364] fix:jans-cli add doc strings --- .../plugins/010_oxauth/edit_client_dialog.py | 2 +- .../plugins/010_oxauth/edit_scope_dialog.py | 2 +- .../plugins/010_oxauth/view_uma_dialog.py | 27 ++++++++++++------- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py b/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py index da69bc96a16..79d2139462b 100755 --- a/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py +++ b/jans-cli-tui/plugins/010_oxauth/edit_client_dialog.py @@ -43,7 +43,7 @@ def __init__(self, parent, title, data, buttons=[], save_handler=None): title (str): The Main dialog title data (list): selected line data button_functions (list, optional): Dialog main buttons with their handlers. Defaults to []. - save_handler (method, optional): _description_. Defaults to None. + save_handler (method, optional): handler invoked when closing the dialog. Defaults to None. """ super().__init__(parent, title, buttons) self.save_handler = save_handler diff --git a/jans-cli-tui/plugins/010_oxauth/edit_scope_dialog.py b/jans-cli-tui/plugins/010_oxauth/edit_scope_dialog.py index cdf706f49d5..b0173132200 100755 --- a/jans-cli-tui/plugins/010_oxauth/edit_scope_dialog.py +++ b/jans-cli-tui/plugins/010_oxauth/edit_scope_dialog.py @@ -64,7 +64,7 @@ def __init__(self, parent, title, data, buttons=[], save_handler=None): title (str): The Main dialog title data (list): selected line data button_functions (list, optional): Dialog main buttons with their handlers. Defaults to []. - save_handler (method, optional): _description_. Defaults to None. + save_handler (method, optional): handler after closing the dialog. Defaults to None. """ super().__init__(parent, title, buttons) self.save_handler = save_handler diff --git a/jans-cli-tui/plugins/010_oxauth/view_uma_dialog.py b/jans-cli-tui/plugins/010_oxauth/view_uma_dialog.py index f5297c77db1..59b12ae6114 100644 --- a/jans-cli-tui/plugins/010_oxauth/view_uma_dialog.py +++ b/jans-cli-tui/plugins/010_oxauth/view_uma_dialog.py @@ -42,9 +42,7 @@ Label, RadioList, TextArea, - CheckboxList, - Shadow, -) + ) from wui_components.jans_dialog_with_nav import JansDialogWithNav from wui_components.jans_nav_bar import JansNavBar from wui_components.jans_side_nav_bar import JansSideNavBar @@ -58,8 +56,22 @@ class ViewUMADialog(JansGDialog, DialogUtils): + """The Main UMA-resources Dialog to view UMA Resource Details + """ def __init__(self, parent, title, data, buttons=[], save_handler=None): + """init for `ViewUMADialog`, inherits from two diffrent classes `JansGDialog` and `DialogUtils` + + JansGDialog (dialog): This is the main dialog Class Widget for all Jans-cli-tui dialogs except custom dialogs like dialogs with navbar + DialogUtils (methods): Responsable for all `make data from dialog` and `check required fields` in the form for any Edit or Add New + + Args: + parent (widget): This is the parent widget for the dialog + title (str): The Main dialog title + data (list): selected line data + button_functions (list, optional): Dialog main buttons with their handlers. Defaults to []. + save_handler (method, optional): handler invoked when closing the dialog. Defaults to None. + """ super().__init__(parent, title, buttons) self.save_handler = save_handler @@ -152,9 +164,9 @@ def cancel(): # width=140, ) - def UMA_prepare_containers(self): - + """Prepare the containers for UMA Dialog + """ self.oauth_main_area = self.UMA_containers['scope'] = HSplit([ self.myparent.getTitledText( _("Scopes"), @@ -179,11 +191,8 @@ def UMA_prepare_containers(self): ],width=D()) - - - def oauth_nav_selection_changed(self, selection): - """This method for the selection change + """This method for the selection change for tabs Args: selection (str): the current selected tab From 4b6ab89d6ed975d2f085de681cb511289dae2e43 Mon Sep 17 00:00:00 2001 From: AbdelwahabAdam Date: Mon, 19 Sep 2022 08:39:20 -0700 Subject: [PATCH 140/364] fix:jans-cli add buttons to the docs and some missing pages --- jans-cli-tui/docs/docs/Gallery/gallery.md | 5 +++- .../docs/models/oauth/edit_client_dialog.md | 1 - .../docs/models/oauth/edit_scope_dialog.md | 1 - jans-cli-tui/docs/docs/models/oauth/oauth.md | 1 - .../docs/plugins/client_api/client_api.md | 1 + .../docs/plugins/config_api/config_api.md | 1 + jans-cli-tui/docs/docs/plugins/fido/fido.md | 1 + .../docs/plugins/oauth/edit_client_dialog.md | 1 + .../docs/plugins/oauth/edit_scope_dialog.md | 1 + .../docs/plugins/oauth/edit_uma_dialog.md | 1 + jans-cli-tui/docs/docs/plugins/oauth/oauth.md | 1 + jans-cli-tui/docs/docs/plugins/plugins.md | 11 +++++++ jans-cli-tui/docs/docs/plugins/scim/scim.md | 1 + .../docs/docs/plugins/scripts/scripts.md | 1 + .../docs/wui_components/wui_components.md | 26 +++++++++-------- jans-cli-tui/mkdocs.yml | 29 +++++++++++++++---- 16 files changed, 62 insertions(+), 21 deletions(-) delete mode 100755 jans-cli-tui/docs/docs/models/oauth/edit_client_dialog.md delete mode 100755 jans-cli-tui/docs/docs/models/oauth/edit_scope_dialog.md delete mode 100755 jans-cli-tui/docs/docs/models/oauth/oauth.md create mode 100755 jans-cli-tui/docs/docs/plugins/client_api/client_api.md create mode 100755 jans-cli-tui/docs/docs/plugins/config_api/config_api.md create mode 100755 jans-cli-tui/docs/docs/plugins/fido/fido.md create mode 100755 jans-cli-tui/docs/docs/plugins/oauth/edit_client_dialog.md create mode 100755 jans-cli-tui/docs/docs/plugins/oauth/edit_scope_dialog.md create mode 100755 jans-cli-tui/docs/docs/plugins/oauth/edit_uma_dialog.md create mode 100755 jans-cli-tui/docs/docs/plugins/oauth/oauth.md create mode 100755 jans-cli-tui/docs/docs/plugins/plugins.md create mode 100755 jans-cli-tui/docs/docs/plugins/scim/scim.md create mode 100755 jans-cli-tui/docs/docs/plugins/scripts/scripts.md diff --git a/jans-cli-tui/docs/docs/Gallery/gallery.md b/jans-cli-tui/docs/docs/Gallery/gallery.md index 2e8da1886e6..a5e81262895 100755 --- a/jans-cli-tui/docs/docs/Gallery/gallery.md +++ b/jans-cli-tui/docs/docs/Gallery/gallery.md @@ -2,4 +2,7 @@ gallery for all possibilities and future improvment for TUI -![Screenshot](docs/img/new_tui/new_tui1.PNG) \ No newline at end of file +![Screenshot](docs/img/new_tui/new_tui1.PNG) + +- [TUI](/gluu-4/docs/Gallery/tui/) +- [CLI](/gluu-4/docs/Gallery/cli/) diff --git a/jans-cli-tui/docs/docs/models/oauth/edit_client_dialog.md b/jans-cli-tui/docs/docs/models/oauth/edit_client_dialog.md deleted file mode 100755 index cca2afdb4e2..00000000000 --- a/jans-cli-tui/docs/docs/models/oauth/edit_client_dialog.md +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/models/oauth/edit_scope_dialog.md b/jans-cli-tui/docs/docs/models/oauth/edit_scope_dialog.md deleted file mode 100755 index 2534cba728b..00000000000 --- a/jans-cli-tui/docs/docs/models/oauth/edit_scope_dialog.md +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/models/oauth/oauth.md b/jans-cli-tui/docs/docs/models/oauth/oauth.md deleted file mode 100755 index da28b945bac..00000000000 --- a/jans-cli-tui/docs/docs/models/oauth/oauth.md +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/plugins/client_api/client_api.md b/jans-cli-tui/docs/docs/plugins/client_api/client_api.md new file mode 100755 index 00000000000..e719802fd0c --- /dev/null +++ b/jans-cli-tui/docs/docs/plugins/client_api/client_api.md @@ -0,0 +1 @@ +::: plugins.050_client_api.main \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/plugins/config_api/config_api.md b/jans-cli-tui/docs/docs/plugins/config_api/config_api.md new file mode 100755 index 00000000000..eaed174de6d --- /dev/null +++ b/jans-cli-tui/docs/docs/plugins/config_api/config_api.md @@ -0,0 +1 @@ +::: plugins.040_config_api.main \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/plugins/fido/fido.md b/jans-cli-tui/docs/docs/plugins/fido/fido.md new file mode 100755 index 00000000000..5c30baa3f6b --- /dev/null +++ b/jans-cli-tui/docs/docs/plugins/fido/fido.md @@ -0,0 +1 @@ +::: plugins.020_fido.main \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/plugins/oauth/edit_client_dialog.md b/jans-cli-tui/docs/docs/plugins/oauth/edit_client_dialog.md new file mode 100755 index 00000000000..1b901865e20 --- /dev/null +++ b/jans-cli-tui/docs/docs/plugins/oauth/edit_client_dialog.md @@ -0,0 +1 @@ +::: plugins.010_oxauth.edit_client_dialog \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/plugins/oauth/edit_scope_dialog.md b/jans-cli-tui/docs/docs/plugins/oauth/edit_scope_dialog.md new file mode 100755 index 00000000000..61a675cd85d --- /dev/null +++ b/jans-cli-tui/docs/docs/plugins/oauth/edit_scope_dialog.md @@ -0,0 +1 @@ +::: plugins.010_oxauth.edit_scope_dialog \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/plugins/oauth/edit_uma_dialog.md b/jans-cli-tui/docs/docs/plugins/oauth/edit_uma_dialog.md new file mode 100755 index 00000000000..edccbaca4a4 --- /dev/null +++ b/jans-cli-tui/docs/docs/plugins/oauth/edit_uma_dialog.md @@ -0,0 +1 @@ +::: plugins.010_oxauth.view_uma_dialog \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/plugins/oauth/oauth.md b/jans-cli-tui/docs/docs/plugins/oauth/oauth.md new file mode 100755 index 00000000000..747de3264aa --- /dev/null +++ b/jans-cli-tui/docs/docs/plugins/oauth/oauth.md @@ -0,0 +1 @@ +::: plugins.010_oxauth.main \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/plugins/plugins.md b/jans-cli-tui/docs/docs/plugins/plugins.md new file mode 100755 index 00000000000..a2c55b521eb --- /dev/null +++ b/jans-cli-tui/docs/docs/plugins/plugins.md @@ -0,0 +1,11 @@ +# plugins +#### There are six main plugins in the TUI and user may add other plugins +---------------------------- +### The Main Plugins are +- [Open Authorization (OAuth)](/gluu-4/docs/plugins/oauth/oauth/) +- [Fast IDentity Online (FIDO)](/gluu-4/docs/plugins/fido/fido/) +- [ System for Cross-domain Identity Management (SCIM)](/gluu-4/docs/plugins/scim/scim/) +- [Config API (Config API)](/gluu-4/docs/plugins/config_api/config_api/) +- [Client API (Client API)](/gluu-4/docs/plugins/client_api/client_api/) +- [Scripts (Scripts)](/gluu-4/docs/plugins/scripts/scripts/) + diff --git a/jans-cli-tui/docs/docs/plugins/scim/scim.md b/jans-cli-tui/docs/docs/plugins/scim/scim.md new file mode 100755 index 00000000000..7ede220362b --- /dev/null +++ b/jans-cli-tui/docs/docs/plugins/scim/scim.md @@ -0,0 +1 @@ +::: plugins.030_scim.main \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/plugins/scripts/scripts.md b/jans-cli-tui/docs/docs/plugins/scripts/scripts.md new file mode 100755 index 00000000000..6cb17b690ac --- /dev/null +++ b/jans-cli-tui/docs/docs/plugins/scripts/scripts.md @@ -0,0 +1 @@ +::: plugins.060_scripts.main \ No newline at end of file diff --git a/jans-cli-tui/docs/docs/wui_components/wui_components.md b/jans-cli-tui/docs/docs/wui_components/wui_components.md index 42cb9d79dc0..8ae69e78b5e 100755 --- a/jans-cli-tui/docs/docs/wui_components/wui_components.md +++ b/jans-cli-tui/docs/docs/wui_components/wui_components.md @@ -2,24 +2,26 @@ ## Content -This Sction contains all the wui components +This Sction contains all the wui components (Classes, attr and methods) **there are diffrent types such as** -### Dialogs components +### - Dialogs components +- [cli dialog (jans_cli_dialog)](/gluu-4/docs/wui_components/jans_cli_dialog) +- [dialog with navigation bar (dialog_with_nav)](/gluu-4/docs/wui_components/jans_dialog_with_nav) +- [message dialog (jans_message_dialog)](/gluu-4/docs/wui_components/jans_message_dialog) -- jans_cli_dialog -- jans_dialog_with_nav -- jans_message_dialog +### - Navigation bar components -### Navigation bar components +- [Main navigation bar (jans_nav_bar)](/gluu-4/docs/wui_components/jans_nav_bar) +- [Horizontal navigation bar (jans_side_nav_bar)](/gluu-4/docs/wui_components/jans_side_nav_bar) +- [vetrical navigation bar (jans_vetrical_nav)](/gluu-4/docs/wui_components/jans_vetrical_nav) -- jans_nav_bar -- jans_side_nav_bar -- jans_vetrical_nav -### Custom components -- jans_data_picker -- jans_drop_down +### - Custom components + +- [data picker widget (jans_data_picker)](/gluu-4/docs/wui_components/jans_data_picker) +- [drop-down widget (jans_drop_down)](/gluu-4/docs/wui_components/jans_drop_down) +