From 2f69a8a90747e6c0b67eb76f66edbf3166019264 Mon Sep 17 00:00:00 2001 From: Mustafa Baser Date: Tue, 25 Jan 2022 23:08:54 +0300 Subject: [PATCH] fix: vm setup suse fixes (#705) * fix: suse fixes * refactor(admin-ui): remove admin-ui (ref: #691) * fix: remove cb and spanner backend options for package * refactor(ce-setup): ce-setup exclude jwt from config-cli --- jans-ce-setup/install.py | 23 +- jans-ce-setup/setup.py | 4 - jans-ce-setup/setup_app/config.py | 3 - .../setup_app/installers/config_api.py | 41 +- jans-ce-setup/setup_app/installers/httpd.py | 14 +- jans-ce-setup/setup_app/installers/jans.py | 2 - .../setup_app/installers/jans_cli.py | 5 + jans-ce-setup/setup_app/installers/node.py | 61 -- jans-ce-setup/setup_app/installers/rdbm.py | 4 +- jans-ce-setup/setup_app/setup_options.py | 3 - jans-ce-setup/setup_app/utils/arg_parser.py | 2 - jans-ce-setup/setup_app/utils/db_utils.py | 18 +- .../setup_app/utils/properties_utils.py | 43 +- jans-ce-setup/templates/system_profile_init | 7 +- .../templates/system_profile_systemd | 1 - jans-cli/cli/pylib/jwt/AUTHORS.rst | 7 - jans-cli/cli/pylib/jwt/LICENSE | 21 - jans-cli/cli/pylib/jwt/__init__.py | 67 -- jans-cli/cli/pylib/jwt/algorithms.py | 619 ------------------ jans-cli/cli/pylib/jwt/api_jwk.py | 97 --- jans-cli/cli/pylib/jwt/api_jws.py | 256 -------- jans-cli/cli/pylib/jwt/api_jwt.py | 222 ------- jans-cli/cli/pylib/jwt/exceptions.py | 66 -- jans-cli/cli/pylib/jwt/help.py | 60 -- jans-cli/cli/pylib/jwt/jwks_client.py | 59 -- jans-cli/cli/pylib/jwt/py.typed | 0 jans-cli/cli/pylib/jwt/utils.py | 100 --- 27 files changed, 58 insertions(+), 1747 deletions(-) delete mode 100644 jans-ce-setup/setup_app/installers/node.py delete mode 100644 jans-cli/cli/pylib/jwt/AUTHORS.rst delete mode 100644 jans-cli/cli/pylib/jwt/LICENSE delete mode 100644 jans-cli/cli/pylib/jwt/__init__.py delete mode 100644 jans-cli/cli/pylib/jwt/algorithms.py delete mode 100644 jans-cli/cli/pylib/jwt/api_jwk.py delete mode 100644 jans-cli/cli/pylib/jwt/api_jws.py delete mode 100644 jans-cli/cli/pylib/jwt/api_jwt.py delete mode 100644 jans-cli/cli/pylib/jwt/exceptions.py delete mode 100644 jans-cli/cli/pylib/jwt/help.py delete mode 100644 jans-cli/cli/pylib/jwt/jwks_client.py delete mode 100644 jans-cli/cli/pylib/jwt/py.typed delete mode 100644 jans-cli/cli/pylib/jwt/utils.py diff --git a/jans-ce-setup/install.py b/jans-ce-setup/install.py index 12c6cc80864..8911d3ec040 100644 --- a/jans-ce-setup/install.py +++ b/jans-ce-setup/install.py @@ -24,8 +24,6 @@ "JYTHON_VERSION": "2.7.3", "OPENDJ_VERSION": "4.4.12", "SETUP_BRANCH": "main", - "ADMIN_UI_FRONTEND_BRANCH": "main", - "NODE_VERSION": "v14.18.2" } jans_dir = '/opt/jans' @@ -50,6 +48,7 @@ parser.add_argument('--jans-app-version', help="Version for Jannses applications") parser.add_argument('--jans-build', help="Buid version for Janssen applications") parser.add_argument('--setup-branch', help="Jannsen setup github branch") +parser.add_argument('--no-setup', help="Do not launch setup", action='store_true') if '-a' in sys.argv: parser.add_argument('--jetty-version', help="Jetty verison. For example 11.0.6") @@ -99,7 +98,7 @@ print("Can't continue...") sys.exit() -os.system('{} install -y {}'.format(package_installer, ' '.join(package_dependencies))) + os.system('{} install -y {}'.format(package_installer, ' '.join(package_dependencies))) def extract_subdir(zip_fn, sub_dir, target_dir, zipf=None): @@ -221,7 +220,6 @@ def download_gcs(): download('https://corretto.aws/downloads/resources/{0}/amazon-corretto-{0}-linux-x64.tar.gz'.format(app_versions['AMAZON_CORRETTO_VERSION']), os.path.join(app_dir, 'amazon-corretto-{0}-linux-x64.tar.gz'.format(app_versions['AMAZON_CORRETTO_VERSION']))) download('https://repo1.maven.org/maven2/org/eclipse/jetty/{1}/{0}/{1}-{0}.tar.gz'.format(app_versions['JETTY_VERSION'], jetty_dist_string), os.path.join(app_dir,'{1}-{0}.tar.gz'.format(app_versions['JETTY_VERSION'], jetty_dist_string))) download('https://maven.gluu.org/maven/org/gluufederation/jython-installer/{0}/jython-installer-{0}.jar'.format(app_versions['JYTHON_VERSION']), os.path.join(app_dir, 'jython-installer-{0}.jar'.format(app_versions['JYTHON_VERSION']))) - download('https://nodejs.org/dist/{0}/node-{0}-linux-x64.tar.xz'.format(app_versions['NODE_VERSION']), os.path.join(app_dir, 'node-{0}-linux-x64.tar.xz'.format(app_versions['NODE_VERSION']))) download(urljoin(maven_base_url, 'jans-auth-server/{0}{1}/jans-auth-server-{0}{1}.war'.format(app_versions['JANS_APP_VERSION'], app_versions['JANS_BUILD'])), os.path.join(jans_app_dir, 'jans-auth.war')) download(urljoin(maven_base_url, 'jans-auth-client/{0}{1}/jans-auth-client-{0}{1}-jar-with-dependencies.jar'.format(app_versions['JANS_APP_VERSION'], app_versions['JANS_BUILD'])), os.path.join(jans_app_dir, 'jans-auth-client-jar-with-dependencies.jar')) download(urljoin(maven_base_url, 'jans-config-api-server/{0}{1}/jans-config-api-server-{0}{1}.war'.format(app_versions['JANS_APP_VERSION'], app_versions['JANS_BUILD'])), os.path.join(jans_app_dir, 'jans-config-api.war')) @@ -229,9 +227,8 @@ def download_gcs(): download(urljoin(maven_base_url, 'scim-plugin/{0}{1}/scim-plugin-{0}{1}-distribution.jar'.format(app_versions['JANS_APP_VERSION'], app_versions['JANS_BUILD'])), os.path.join(jans_app_dir, 'scim-plugin.jar')) download('https://ox.gluu.org/icrby8xcvbcv/cli-swagger/jca.tgz', os.path.join(jans_app_dir, 'jca-swagger-client.tgz')) download('https://ox.gluu.org/icrby8xcvbcv/cli-swagger/scim.tgz', os.path.join(jans_app_dir, 'scim-swagger-client.tgz')) - download(urljoin(maven_base_url, 'admin-ui-plugin/{0}{1}/admin-ui-plugin-{0}{1}-distribution.jar'.format(app_versions['JANS_APP_VERSION'], app_versions['JANS_BUILD'])), os.path.join(jans_app_dir, 'admin-ui-plugin-distribution.jar')) - download('https://github.com/GluuFederation/gluu-admin-ui/archive/refs/heads/{}.zip'.format(app_versions['ADMIN_UI_FRONTEND_BRANCH']), os.path.join(jans_app_dir, 'gluu-admin-ui.zip')) download('https://raw.githubusercontent.com/GluuFederation/gluu-snap/master/facter/facter', os.path.join(jans_app_dir, 'facter')) + download('https://github.com/jpadilla/pyjwt/archive/refs/tags/2.3.0.zip', os.path.join(app_dir, 'pyjwt.zip')) if argsp.profile == 'jans': download('https://maven.gluu.org/maven/org/gluufederation/opendj/opendj-server-legacy/{0}/opendj-server-legacy-{0}.zip'.format(app_versions['OPENDJ_VERSION']), os.path.join(app_dir, 'opendj-server-legacy-{0}.zip'.format(app_versions['OPENDJ_VERSION']))) @@ -348,7 +345,7 @@ def profile_setup(): print("Stopping OpenDj Server") os.system('/opt/opendj/bin/stop-ds') - remove_list = ['/etc/certs', '/etc/jans', '/opt/jans', '/opt/amazon-corretto*', '/opt/node*', '/opt/jre', '/opt/jetty*', '/opt/jython*'] + remove_list = ['/etc/certs', '/etc/jans', '/opt/jans', '/opt/amazon-corretto*', '/opt/jre', '/opt/jetty*', '/opt/jython*'] if argsp.profile == 'jans': remove_list.append('/opt/opendj') if not argsp.keep_downloads: @@ -377,16 +374,16 @@ def profile_setup(): profile_setup() extract_file(jans_zip_file, 'jans-config-api/server/src/main/resources/log4j2.xml', jans_app_dir) - extract_file(jans_zip_file, 'jans-config-api/plugins/admin-ui-plugin/config/log4j2-adminui.xml', jans_app_dir) print("Preparing jans-cli package") extract_subdir(jans_zip_file, 'jans-cli', 'jans-cli', os.path.join(jans_app_dir, 'jans-cli.zip')) - print("Launching Janssen Setup") + if not argsp.no_setup: + print("Launching Janssen Setup") - setup_cmd = 'python3 {}/setup.py'.format(setup_dir) + setup_cmd = 'python3 {}/setup.py'.format(setup_dir) - if argsp.args: - setup_cmd += ' ' + argsp.args + if argsp.args: + setup_cmd += ' ' + argsp.args - os.system(setup_cmd) + os.system(setup_cmd) diff --git a/jans-ce-setup/setup.py b/jans-ce-setup/setup.py index e5d91091e34..7d27cc552d9 100755 --- a/jans-ce-setup/setup.py +++ b/jans-ce-setup/setup.py @@ -55,7 +55,6 @@ from setup_app.installers.jre import JreInstaller from setup_app.installers.jetty import JettyInstaller from setup_app.installers.jython import JythonInstaller -from setup_app.installers.node import NodeInstaller from setup_app.installers.jans_auth import JansAuthInstaller if Config.profile == 'jans': @@ -156,7 +155,6 @@ jreInstaller = JreInstaller() jettyInstaller = JettyInstaller() jythonInstaller = JythonInstaller() -nodeInstaller = NodeInstaller() if Config.profile == 'jans': openDjInstaller = OpenDjInstaller() @@ -270,8 +268,6 @@ def do_installation(): jreInstaller.start_installation() jettyInstaller.start_installation() jythonInstaller.start_installation() - if Config.installAdminUI: - nodeInstaller.start_installation() jansInstaller.copy_scripts() jansInstaller.encode_passwords() diff --git a/jans-ce-setup/setup_app/config.py b/jans-ce-setup/setup_app/config.py index deb9ef8b868..56f5f168c47 100644 --- a/jans-ce-setup/setup_app/config.py +++ b/jans-ce-setup/setup_app/config.py @@ -23,7 +23,6 @@ class Config: etc_hostname = '/etc/hostname' osDefault = '/etc/default' sysemProfile = '/etc/profile' - node_home = '/opt/node' jython_home = '/opt/jython' ldapBaseFolder = '/opt/opendj' network = '/etc/sysconfig/network' @@ -171,7 +170,6 @@ def progress(self, service_name, msg, incr=False): self.installJans = True self.installJre = True self.installJetty = True - self.installNode = False self.installJython = True self.installOxAuth = True self.installOxTrust = True @@ -189,7 +187,6 @@ def progress(self, service_name, msg, incr=False): self.installJansCli = False self.loadTestData = False self.allowPreReleasedFeatures = False - self.installAdminUI = False # backward compatibility self.os_type = base.os_type diff --git a/jans-ce-setup/setup_app/installers/config_api.py b/jans-ce-setup/setup_app/installers/config_api.py index 8ac628b6e4a..abd64616f38 100644 --- a/jans-ce-setup/setup_app/installers/config_api.py +++ b/jans-ce-setup/setup_app/installers/config_api.py @@ -38,20 +38,15 @@ def __init__(self): self.load_ldif_files = [self.config_ldif_fn, self.scope_ldif_fn] self.libDir = os.path.join(self.jetty_base, self.service_name, 'custom/libs/') self.custom_config_dir = os.path.join(self.jetty_base, self.service_name, 'custom/config') - self.admin_ui_config_properties = os.path.join(self.output_folder, 'auiConfiguration.properties') self.source_files = [ (os.path.join(Config.distJansFolder, 'jans-config-api.war'), 'https://maven.jans.io/maven/io/jans/jans-config-api-server/{0}/jans-config-api-server-{0}.war'.format(Config.oxVersion)), (os.path.join(Config.distJansFolder, 'scim-plugin.jar'), 'https://maven.jans.io/maven/io/jans/scim-plugin/{0}/scim-plugin-{0}-distribution.jar'.format(Config.oxVersion)), - (os.path.join(Config.distJansFolder, 'admin-ui-plugin-distribution.jar'), 'https://maven.jans.io/maven/io/jans/admin-ui-plugin/{0}/admin-ui-plugin-{0}-distribution.jar'.format(Config.oxVersion)), - (os.path.join(Config.distJansFolder, 'log4j2.xml'), 'https://raw.githubusercontent.com/JanssenProject/jans-config-api/master/server/src/main/resources/log4j2.xml'), - (os.path.join(Config.distJansFolder, 'log4j2-adminui.xml'), 'https://raw.githubusercontent.com/JanssenProject/jans-config-api/master/plugins/admin-ui-plugin/config/log4j2-adminui.xml'), - (os.path.join(Config.distJansFolder, 'gluu-admin-ui.zip'), 'https://github.com/GluuFederation/gluu-admin-ui/archive/refs/heads/main.zip'), (os.path.join(Config.distJansFolder, 'facter'), 'https://raw.githubusercontent.com/GluuFederation/gluu-snap/master/facter/facter'), ] def install(self): - self.copyFile(self.source_files[6][0], '/usr/sbin') + self.copyFile(self.source_files[2][0], '/usr/sbin') self.run([paths.cmd_chmod, '+x', '/usr/sbin/facter']) self.installJettyService(self.jetty_app_configuration[self.service_name], True) self.logIt("Copying fido.war into jetty webapps folder...") @@ -61,8 +56,6 @@ def install(self): self.copyFile(self.source_files[1][0], self.libDir) scim_plugin_path = os.path.join(self.libDir, os.path.basename(self.source_files[1][0])) self.add_extra_class(scim_plugin_path) - if Config.installAdminUI: - self.install_admin_ui_frontend() self.enable() def installed(self): @@ -244,35 +237,3 @@ def load_test_data(self): self.writeFile(out_fn, rendered_text) self.dbUtils.import_ldif([out_fn]) - def service_post_setup(self): - if Config.installAdminUI: - self.logIt("Installing Jans Admin UI", pbar=self.service_name) - self.check_clients([('role_based_client_id', '2000.')]) - self.renderTemplateInOut(self.admin_ui_config_properties, self.templates_folder, self.output_folder) - self.copyFile(self.source_files[2][0], self.libDir) - admin_ui_plugin_path = os.path.join(self.libDir, os.path.basename(self.source_files[2][0])) - self.add_extra_class(admin_ui_plugin_path) - self.copyFile(self.admin_ui_config_properties, self.custom_config_dir) - - for logfn in (self.source_files[3][0], self.source_files[4][0]): - self.copyFile(logfn, self.custom_config_dir) - - - def install_admin_ui_frontend(self): - self.logIt("Installing Jans Admin UI Frontend", pbar=self.service_name) - package_zip = zipfile.ZipFile(self.source_files[5][0], "r") - package_par_dir = package_zip.namelist()[0] - source_dir = os.path.join(Config.outputFolder, package_par_dir) - package_zip.extractall(Config.outputFolder) - - self.renderTemplateInOut(os.path.join(source_dir, '.env.tmp'), source_dir, source_dir) - self.copyFile(os.path.join(source_dir, '.env.tmp'), os.path.join(source_dir, '.env')) - self.run([paths.cmd_chown, '-R', 'node:node', source_dir]) - cmd_path = 'PATH=$PATH:{}/bin:{}/bin'.format(Config.jre_home, Config.node_home) - - for cmd in ('npm install @openapitools/openapi-generator-cli', 'npm run api', 'npm install', 'npm run plugin:clean', 'npm run build:prod'): - self.logIt("Executing `{}`".format(cmd), pbar=self.service_name) - run_cmd = '{} {}'.format(cmd_path, cmd) - self.run(['/bin/su', 'node','-c', run_cmd], source_dir) - - self.copyTree(os.path.join(source_dir, 'dist'), '/var/www/html/admin') diff --git a/jans-ce-setup/setup_app/installers/httpd.py b/jans-ce-setup/setup_app/installers/httpd.py index 6e62ed050fd..4cc312079fd 100644 --- a/jans-ce-setup/setup_app/installers/httpd.py +++ b/jans-ce-setup/setup_app/installers/httpd.py @@ -36,11 +36,11 @@ def __init__(self): self.apache2_ssl_24_conf = os.path.join(self.output_folder, 'https_jans.conf') if base.os_type == 'suse': - self.https_gluu_fn = '/etc/apache2/vhosts.d/_https_gluu.conf' + self.https_jans_fn = '/etc/apache2/vhosts.d/_https_gluu.conf' elif base.clone_type == 'rpm': - self.https_gluu_fn = '/etc/httpd/conf.d/https_gluu.conf' + self.https_jans_fn = '/etc/httpd/conf.d/https_gluu.conf' else: - self.https_gluu_fn = '/etc/apache2/sites-available/https_gluu.conf' + self.https_jans_fn = '/etc/apache2/sites-available/https_gluu.conf' def configure(self): @@ -180,15 +180,15 @@ def write_httpd_config(self): self.copyFile(self.apache2_ssl_24_conf, '/etc/httpd/conf.d/https_gluu.conf') if base.os_type == 'suse': - self.copyFile(self.apache2_ssl_conf, self.https_gluu_fn) + self.copyFile(self.apache2_ssl_conf, self.https_jans_fn) elif base.clone_type == 'rpm' and base.os_initdaemon == 'init': self.copyFile(self.apache2_conf, '/etc/httpd/conf/httpd.conf') - self.copyFile(self.apache2_ssl_conf, self.https_gluu_fn) + self.copyFile(self.apache2_ssl_conf, self.https_jans_fn) elif base.clone_type == 'deb': - self.copyFile(self.apache2_ssl_conf, self.https_gluu_fn) - self.run([paths.cmd_ln, '-s', self.https_gluu_fn, + self.copyFile(self.apache2_ssl_conf, self.https_jans_fn) + self.run([paths.cmd_ln, '-s', self.https_jans_fn, '/etc/apache2/sites-enabled/https_gluu.conf']) def installed(self): diff --git a/jans-ce-setup/setup_app/installers/jans.py b/jans-ce-setup/setup_app/installers/jans.py index 303ad5e7b89..5cb55a3b8d9 100644 --- a/jans-ce-setup/setup_app/installers/jans.py +++ b/jans-ce-setup/setup_app/installers/jans.py @@ -68,10 +68,8 @@ def __repr__(self): txt += 'Install Apache 2 web server'.ljust(30) + repr(Config.installHttpd).rjust(35) + (' *' if 'installHttpd' in Config.addPostSetupService else '') + "\n" - txt += 'Install Node'.ljust(30) + repr(Config.installAdminUI).rjust(35) + "\n" txt += 'Install Auth Server'.ljust(30) + repr(Config.installOxAuth).rjust(35) + "\n" txt += 'Install Jans Auth Config Api'.ljust(30) + repr(Config.installConfigApi).rjust(35) + "\n" - txt += 'Install Jans Admin UI'.ljust(30) + repr(Config.installAdminUI).rjust(35) + "\n" if Config.profile == 'jans': txt += 'Install Fido2 Server'.ljust(30) + repr(Config.installFido2).rjust(35) + (' *' if 'installFido2' in Config.addPostSetupService else '') + "\n" txt += 'Install Scim Server'.ljust(30) + repr(Config.installScimServer).rjust(35) + (' *' if 'installScimServer' in Config.addPostSetupService else '') + "\n" diff --git a/jans-ce-setup/setup_app/installers/jans_cli.py b/jans-ce-setup/setup_app/installers/jans_cli.py index ecd4070aed2..34b90387056 100644 --- a/jans-ce-setup/setup_app/installers/jans_cli.py +++ b/jans-ce-setup/setup_app/installers/jans_cli.py @@ -40,6 +40,7 @@ def __init__(self): (os.path.join(Config.distJansFolder, 'jans-cli.zip'), 'https://api.github.com/repos/JanssenProject/jans-cli/tarball/main'.format(Config.oxVersion)), (os.path.join(Config.distJansFolder, 'jca-swagger-client.tgz'), 'https://ox.gluu.org/icrby8xcvbcv/cli-swagger/jca.tgz'), (os.path.join(Config.distJansFolder, 'scim-swagger-client.tgz'), 'https://ox.gluu.org/icrby8xcvbcv/cli-swagger/scim.tgz'), + (os.path.join(Config.distAppFolder, 'pyjwt.zip'), 'https://github.com/jpadilla/pyjwt/archive/refs/tags/2.3.0.zip'), ] def install(self): @@ -60,6 +61,10 @@ def install(self): self.writeFile(init_fn, '') shutil.unpack_archive(self.source_files[i+1][0], swagger_cli_dir) + #extract pyjwt from archieve + base.extract_from_zip(self.source_files[3][0], 'jwt', os.path.join(self.jans_cli_install_dir, 'pylib/jwt')) + + def generate_configuration(self): self.check_clients([('role_based_client_id', '2000.')]) diff --git a/jans-ce-setup/setup_app/installers/node.py b/jans-ce-setup/setup_app/installers/node.py deleted file mode 100644 index f5bf98789c5..00000000000 --- a/jans-ce-setup/setup_app/installers/node.py +++ /dev/null @@ -1,61 +0,0 @@ -import os -import glob - -from setup_app import paths -from setup_app.utils import base -from setup_app.static import AppType, InstallOption -from setup_app.config import Config -from setup_app.utils.setup_utils import SetupUtils -from setup_app.installers.base import BaseInstaller - -class NodeInstaller(BaseInstaller, SetupUtils): - - """This installer provides node installtion for Jans server.""" - - node_base = os.path.join(Config.jansOptFolder, 'node') - templates_rendered = False - - def __init__(self): - """Inits NodeInstaller instance.""" - self.service_name = 'node' - self.needdb = False # we don't need backend connection in this class - self.install_var = 'installNode' - self.app_type = AppType.APPLICATION - self.install_type = InstallOption.MONDATORY - if not base.snap: - self.register_progess() - - self.node_user_home = '/home/node' - - def install(self): - - node_archieve_list = glob.glob(os.path.join(Config.distAppFolder, 'node-*-linux-x64.tar.xz')) - - if not node_archieve_list: - self.logIt("Can't find node archive", True, True) - - if not base.snap: - self.createUser('node', self.node_user_home) - self.addUserToGroup('jans', 'node') - - nodeArchive = max(node_archieve_list) - - try: - self.logIt("Extracting %s into /opt" % nodeArchive) - self.run([paths.cmd_tar, '-xJf', nodeArchive, '-C', '/opt/', '--no-xattrs', '--no-same-owner', '--no-same-permissions']) - except Exception as e: - self.logIt("Error encountered while extracting archive {}: {}".format(nodeArchive, e)) - - nodeDestinationPath = max(glob.glob('/opt/node-*-linux-x64')) - - self.run([paths.cmd_ln, '-sf', nodeDestinationPath, Config.node_home]) - self.run([paths.cmd_chmod, '-R', "755", "%s/bin/" % nodeDestinationPath]) - - # Create temp folder - self.run([paths.cmd_mkdir, '-p', "%s/temp" % Config.node_home]) - - self.run([paths.cmd_chown, '-R', 'node:node', nodeDestinationPath]) - self.run([paths.cmd_chown, '-h', 'node:node', Config.node_home]) - - self.run([paths.cmd_mkdir, '-p', self.node_base]) - self.run([paths.cmd_chown, '-R', 'node:node', self.node_base]) diff --git a/jans-ce-setup/setup_app/installers/rdbm.py b/jans-ce-setup/setup_app/installers/rdbm.py index 1389ffde066..9628331ee55 100644 --- a/jans-ce-setup/setup_app/installers/rdbm.py +++ b/jans-ce-setup/setup_app/installers/rdbm.py @@ -54,7 +54,9 @@ def local_install(self): packageUtils.check_and_install_packages() if Config.rdbm_type == 'mysql': - if base.clone_type == 'rpm': + if base.os_type == 'suse': + self.restart('mysql') + elif base.clone_type == 'rpm': self.restart('mysqld') result, conn = self.dbUtils.mysqlconnection(log=False) if not result: diff --git a/jans-ce-setup/setup_app/setup_options.py b/jans-ce-setup/setup_app/setup_options.py index d22b2485a44..6c4fbdb9d7e 100644 --- a/jans-ce-setup/setup_app/setup_options.py +++ b/jans-ce-setup/setup_app/setup_options.py @@ -107,9 +107,6 @@ def get_setup_options(): if base.argsp.ldap_admin_password: setupOptions['ldapPass'] = base.argsp.ldap_admin_password - if base.argsp.install_admin_ui: - setupOptions['installAdminUI'] = True - if base.argsp.admin_password: setupOptions['admin_password'] = base.argsp.admin_password elif base.argsp.ldap_admin_password: diff --git a/jans-ce-setup/setup_app/utils/arg_parser.py b/jans-ce-setup/setup_app/utils/arg_parser.py index 98955514a8c..8e0e0f8b468 100644 --- a/jans-ce-setup/setup_app/utils/arg_parser.py +++ b/jans-ce-setup/setup_app/utils/arg_parser.py @@ -82,8 +82,6 @@ def arg_parser(): parser.add_argument('-approved-issuer', help="Api Approved Issuer") - parser.add_argument('--install-admin-ui', help="Install Gluu Admin UI", action='store_true') - argsp = parser.parse_args() return argsp diff --git a/jans-ce-setup/setup_app/utils/db_utils.py b/jans-ce-setup/setup_app/utils/db_utils.py index 029adb2c471..697e6f3f3ec 100644 --- a/jans-ce-setup/setup_app/utils/db_utils.py +++ b/jans-ce-setup/setup_app/utils/db_utils.py @@ -39,6 +39,7 @@ class DBUtils: Base = None session = None cbm = None + mariadb = False def bind(self, use_ssl=True, force=False): @@ -118,8 +119,16 @@ def sqlconnection(self, log=True): Session = sqlalchemy.orm.sessionmaker(bind=self.engine) self.session = Session() self.metadata = sqlalchemy.MetaData() - self.session.connection() + myconn = self.session.connection() + + # are we running on MariaDB? + query = myconn.execute("select version()") + result = query.first() + if result and 'mariadb' in result[0].lower(): + self.mariadb = True + base.logIt("{} Connection was successful".format(Config.rdbm_type.upper())) + return True, self.session except Exception as e: @@ -129,6 +138,8 @@ def sqlconnection(self, log=True): @property def json_dialects_instance(self): + if self.mariadb: + return sqlalchemy.dialects.mysql.LONGTEXT return sqlalchemy.dialects.mysql.json.JSON if Config.rdbm_type == 'mysql' else sqlalchemy.dialects.postgresql.json.JSON def mysqlconnection(self, log=True): @@ -880,7 +891,10 @@ def import_ldif(self, ldif_files, bucket=None, force=None): sqlalchObj = sqlalchCls() for v in vals: - setattr(sqlalchObj, v, vals[v]) + vval = vals[v] + if self.mariadb and isinstance(vval, dict): + vval = json.dumps(vals[v]) + setattr(sqlalchObj, v, vval) base.logIt("Adding {}".format(sqlalchObj.doc_id)) self.session.add(sqlalchObj) diff --git a/jans-ce-setup/setup_app/utils/properties_utils.py b/jans-ce-setup/setup_app/utils/properties_utils.py index 507d8380048..ce9faa00ce4 100644 --- a/jans-ce-setup/setup_app/utils/properties_utils.py +++ b/jans-ce-setup/setup_app/utils/properties_utils.py @@ -298,7 +298,7 @@ def getString(value): if obj_name.startswith('cmd_'): continue - + if not obj_name.startswith('__') and (not callable(obj)): if obj_name == 'mappingLocations': @@ -313,15 +313,15 @@ def getString(value): # TODO: uncomment later return - + self.run([paths.cmd_openssl, 'enc', '-aes-256-cbc', '-in', prop_fn, '-out', prop_fn+'.enc', '-k', Config.admin_password]) - + Config.post_messages.append( "Encrypted properties file saved to {0}.enc with password {1}\nDecrypt the file with the following command if you want to re-use:\nopenssl enc -d -aes-256-cbc -in {2}.enc -out {3}".format( prop_fn, Config.admin_password, os.path.basename(prop_fn), os.path.basename(Config.setup_properties_fn))) - + self.run(['rm', '-f', prop_fn]) - + except: self.logIt("Error saving properties", True) @@ -383,7 +383,7 @@ def test_cb_servers(self, couchbase_hostname): return retval def prompt_remote_couchbase(self): - + while True: Config.couchbase_hostname = self.getPrompt(" Couchbase hosts", Config.get('couchbase_hostname')) Config.couchebaseClusterAdmin = self.getPrompt(" Couchbase User", Config.get('couchebaseClusterAdmin')) @@ -589,22 +589,6 @@ def promptForConfigApi(self): if Config.installed_instance and Config.installConfigApi: Config.addPostSetupService.append('installConfigApi') - - - def promptAdminUI(self): - if Config.installed_instance and Config.installAdminUI: - return - - promptForAdminUI = self.getPrompt("Install Jans Admin UI?", - self.getDefaultOption(Config.installAdminUI) - )[0].lower() - - Config.installAdminUI = True if promptForAdminUI == 'y' else False - - if Config.installed_instance and Config.promptForAdminUI: - Config.addPostSetupService.append('installAdminUI') - - def prompt_for_rdbm(self): while True: Config.rdbm_type = self.getPrompt("RDBM Type", Config.rdbm_type) @@ -641,17 +625,17 @@ def prompt_for_rdbm(self): def prompt_for_backend(self): print('Chose Backend Type:') - + backend_types = ['Local OpenDj', 'Remote OpenDj', - 'Remote Couchbase', 'Local MySQL', 'Remote MySQL', - 'Cloud Spanner', ] - if 'couchbase' in self.getBackendTypes(): - backend_types.insert(2, 'Local Couchbase') + if not os.path.exists(os.path.join(Config.install_dir, 'package')): + backend_types += ['Remote Couchbase', 'Cloud Spanner'] + if 'couchbase' in self.getBackendTypes(): + backend_types.insert(2, 'Local Couchbase') nlist = [] for i, btype in enumerate(backend_types): @@ -739,10 +723,10 @@ def prompt_for_backend(self): Config.rdbm_install_type = InstallTypes.LOCAL Config.rdbm_type = 'mysql' Config.rdbm_host = 'localhost' - Config.rdbm_user = 'gluu' + Config.rdbm_user = 'jans' Config.rdbm_password = self.getPW(special='.*=+-()[]{}') Config.rdbm_port = 3306 - Config.rdbm_db = 'gluudb' + Config.rdbm_db = 'jansdb' elif backend_type_str == 'Remote MySQL': Config.opendj_install = InstallTypes.NONE @@ -908,7 +892,6 @@ def promptForProperties(self): Config.admin_password = adminPass self.promptForConfigApi() - self.promptAdminUI() self.promptForScimServer() self.promptForFido2Server() self.promptForEleven() diff --git a/jans-ce-setup/templates/system_profile_init b/jans-ce-setup/templates/system_profile_init index ab44621aa71..52ce8821e2e 100644 --- a/jans-ce-setup/templates/system_profile_init +++ b/jans-ce-setup/templates/system_profile_init @@ -2,14 +2,13 @@ # Added by Jans to prevent user to login into chroot container without starting jans-server service # ##################################################################################################### if [ "$(ls -A /dev/pts/)" != "" ] && [ "$(ls -A /proc/)" != "" ] && [ "$(ls -A /lib/modules/)" != "" ] && [ "$(ls -A /sys/class/net/lo/)" != "" ] && [ "$(ls /dev/ | grep -vE 'null|pts')" != "" ]; then - echo "Welcome to the Jans Server!" + echo "Welcome to the Jans Server!" else - echo "Jans server is not started, please start it from root machine by service jans-server start" - exit + echo "Jans server is not started, please start it from root machine by service jans-server start" + exit fi export JAVA_HOME=%(jre_home)s -export NODE_HOME=%(node_home)s export OPENDJ_JAVA_HOME=%(jre_home)s export PATH=$PATH:$JAVA_HOME/bin:$NODE_HOME/bin:%(ldapBaseFolder)s/bin diff --git a/jans-ce-setup/templates/system_profile_systemd b/jans-ce-setup/templates/system_profile_systemd index 8670066dc17..bfe098cf928 100644 --- a/jans-ce-setup/templates/system_profile_systemd +++ b/jans-ce-setup/templates/system_profile_systemd @@ -3,7 +3,6 @@ ######################################################################## export JAVA_HOME=%(jre_home)s -export NODE_HOME=%(node_home)s export OPENDJ_JAVA_HOME=%(jre_home)s export PATH=$PATH:$JAVA_HOME/bin:$NODE_HOME/bin:%(ldapBaseFolder)s/bin diff --git a/jans-cli/cli/pylib/jwt/AUTHORS.rst b/jans-cli/cli/pylib/jwt/AUTHORS.rst deleted file mode 100644 index 88e2b6ad752..00000000000 --- a/jans-cli/cli/pylib/jwt/AUTHORS.rst +++ /dev/null @@ -1,7 +0,0 @@ -Authors -======= - -``pyjwt`` is currently written and maintained by `Jose Padilla `_. -Originally written and maintained by `Jeff Lindsay `_. - -A full list of contributors can be found on GitHub’s `overview `_. diff --git a/jans-cli/cli/pylib/jwt/LICENSE b/jans-cli/cli/pylib/jwt/LICENSE deleted file mode 100644 index bdc7819ea10..00000000000 --- a/jans-cli/cli/pylib/jwt/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2015 José Padilla - -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/cli/pylib/jwt/__init__.py b/jans-cli/cli/pylib/jwt/__init__.py deleted file mode 100644 index 0d0805a5af7..00000000000 --- a/jans-cli/cli/pylib/jwt/__init__.py +++ /dev/null @@ -1,67 +0,0 @@ -from .api_jws import ( - PyJWS, - get_unverified_header, - register_algorithm, - unregister_algorithm, -) -from .api_jwt import PyJWT, decode, encode -from .exceptions import ( - DecodeError, - ExpiredSignatureError, - ImmatureSignatureError, - InvalidAlgorithmError, - InvalidAudienceError, - InvalidIssuedAtError, - InvalidIssuerError, - InvalidKeyError, - InvalidSignatureError, - InvalidTokenError, - MissingRequiredClaimError, - PyJWKClientError, - PyJWKError, - PyJWKSetError, - PyJWTError, -) -from .jwks_client import PyJWKClient - -__version__ = "2.0.1" - -__title__ = "PyJWT" -__description__ = "JSON Web Token implementation in Python" -__url__ = "https://pyjwt.readthedocs.io" -__uri__ = __url__ -__doc__ = __description__ + " <" + __uri__ + ">" - -__author__ = "José Padilla" -__email__ = "hello@jpadilla.com" - -__license__ = "MIT" -__copyright__ = "Copyright 2015-2020 José Padilla" - - -__all__ = [ - "PyJWS", - "PyJWT", - "PyJWKClient", - "decode", - "encode", - "get_unverified_header", - "register_algorithm", - "unregister_algorithm", - # Exceptions - "DecodeError", - "ExpiredSignatureError", - "ImmatureSignatureError", - "InvalidAlgorithmError", - "InvalidAudienceError", - "InvalidIssuedAtError", - "InvalidIssuerError", - "InvalidKeyError", - "InvalidSignatureError", - "InvalidTokenError", - "MissingRequiredClaimError", - "PyJWKClientError", - "PyJWKError", - "PyJWKSetError", - "PyJWTError", -] diff --git a/jans-cli/cli/pylib/jwt/algorithms.py b/jans-cli/cli/pylib/jwt/algorithms.py deleted file mode 100644 index 50719bea5f8..00000000000 --- a/jans-cli/cli/pylib/jwt/algorithms.py +++ /dev/null @@ -1,619 +0,0 @@ -import hashlib -import hmac -import json - -from .exceptions import InvalidKeyError -from .utils import ( - base64url_decode, - base64url_encode, - der_to_raw_signature, - force_bytes, - from_base64url_uint, - raw_to_der_signature, - to_base64url_uint, -) - -try: - import cryptography.exceptions - from cryptography.exceptions import InvalidSignature - from cryptography.hazmat.primitives import hashes - from cryptography.hazmat.primitives.asymmetric import ec, padding - from cryptography.hazmat.primitives.asymmetric.ec import ( - EllipticCurvePrivateKey, - EllipticCurvePublicKey, - ) - from cryptography.hazmat.primitives.asymmetric.ed25519 import ( - Ed25519PrivateKey, - Ed25519PublicKey, - ) - from cryptography.hazmat.primitives.asymmetric.rsa import ( - RSAPrivateKey, - RSAPrivateNumbers, - RSAPublicKey, - RSAPublicNumbers, - rsa_crt_dmp1, - rsa_crt_dmq1, - rsa_crt_iqmp, - rsa_recover_prime_factors, - ) - from cryptography.hazmat.primitives.serialization import ( - load_pem_private_key, - load_pem_public_key, - load_ssh_public_key, - ) - - has_crypto = True -except ModuleNotFoundError: - has_crypto = False - -requires_cryptography = { - "RS256", - "RS384", - "RS512", - "ES256", - "ES256K", - "ES384", - "ES521", - "ES512", - "PS256", - "PS384", - "PS512", - "EdDSA", -} - - -def get_default_algorithms(): - """ - Returns the algorithms that are implemented by the library. - """ - default_algorithms = { - "none": NoneAlgorithm(), - "HS256": HMACAlgorithm(HMACAlgorithm.SHA256), - "HS384": HMACAlgorithm(HMACAlgorithm.SHA384), - "HS512": HMACAlgorithm(HMACAlgorithm.SHA512), - } - - if has_crypto: - default_algorithms.update( - { - "RS256": RSAAlgorithm(RSAAlgorithm.SHA256), - "RS384": RSAAlgorithm(RSAAlgorithm.SHA384), - "RS512": RSAAlgorithm(RSAAlgorithm.SHA512), - "ES256": ECAlgorithm(ECAlgorithm.SHA256), - "ES256K": ECAlgorithm(ECAlgorithm.SHA256), - "ES384": ECAlgorithm(ECAlgorithm.SHA384), - "ES521": ECAlgorithm(ECAlgorithm.SHA512), - "ES512": ECAlgorithm( - ECAlgorithm.SHA512 - ), # Backward compat for #219 fix - "PS256": RSAPSSAlgorithm(RSAPSSAlgorithm.SHA256), - "PS384": RSAPSSAlgorithm(RSAPSSAlgorithm.SHA384), - "PS512": RSAPSSAlgorithm(RSAPSSAlgorithm.SHA512), - "EdDSA": Ed25519Algorithm(), - } - ) - - return default_algorithms - - -class Algorithm: - """ - The interface for an algorithm used to sign and verify tokens. - """ - - def prepare_key(self, key): - """ - Performs necessary validation and conversions on the key and returns - the key value in the proper format for sign() and verify(). - """ - raise NotImplementedError - - def sign(self, msg, key): - """ - Returns a digital signature for the specified message - using the specified key value. - """ - raise NotImplementedError - - def verify(self, msg, key, sig): - """ - Verifies that the specified digital signature is valid - for the specified message and key values. - """ - raise NotImplementedError - - @staticmethod - def to_jwk(key_obj): - """ - Serializes a given RSA key into a JWK - """ - raise NotImplementedError - - @staticmethod - def from_jwk(jwk): - """ - Deserializes a given RSA key from JWK back into a PublicKey or PrivateKey object - """ - raise NotImplementedError - - -class NoneAlgorithm(Algorithm): - """ - Placeholder for use when no signing or verification - operations are required. - """ - - def prepare_key(self, key): - if key == "": - key = None - - if key is not None: - raise InvalidKeyError('When alg = "none", key value must be None.') - - return key - - def sign(self, msg, key): - return b"" - - def verify(self, msg, key, sig): - return False - - -class HMACAlgorithm(Algorithm): - """ - Performs signing and verification operations using HMAC - and the specified hash function. - """ - - SHA256 = hashlib.sha256 - SHA384 = hashlib.sha384 - SHA512 = hashlib.sha512 - - def __init__(self, hash_alg): - self.hash_alg = hash_alg - - def prepare_key(self, key): - key = force_bytes(key) - - invalid_strings = [ - b"-----BEGIN PUBLIC KEY-----", - b"-----BEGIN CERTIFICATE-----", - b"-----BEGIN RSA PUBLIC KEY-----", - b"ssh-rsa", - ] - - if any(string_value in key for string_value in invalid_strings): - raise InvalidKeyError( - "The specified key is an asymmetric key or x509 certificate and" - " should not be used as an HMAC secret." - ) - - return key - - @staticmethod - def to_jwk(key_obj): - return json.dumps( - { - "k": base64url_encode(force_bytes(key_obj)).decode(), - "kty": "oct", - } - ) - - @staticmethod - def from_jwk(jwk): - try: - if isinstance(jwk, str): - obj = json.loads(jwk) - elif isinstance(jwk, dict): - obj = jwk - else: - raise ValueError - except ValueError: - raise InvalidKeyError("Key is not valid JSON") - - if obj.get("kty") != "oct": - raise InvalidKeyError("Not an HMAC key") - - return base64url_decode(obj["k"]) - - def sign(self, msg, key): - return hmac.new(key, msg, self.hash_alg).digest() - - def verify(self, msg, key, sig): - return hmac.compare_digest(sig, self.sign(msg, key)) - - -if has_crypto: - - class RSAAlgorithm(Algorithm): - """ - Performs signing and verification operations using - RSASSA-PKCS-v1_5 and the specified hash function. - """ - - SHA256 = hashes.SHA256 - SHA384 = hashes.SHA384 - SHA512 = hashes.SHA512 - - def __init__(self, hash_alg): - self.hash_alg = hash_alg - - def prepare_key(self, key): - if isinstance(key, RSAPrivateKey) or isinstance(key, RSAPublicKey): - return key - - if isinstance(key, (bytes, str)): - key = force_bytes(key) - - try: - if key.startswith(b"ssh-rsa"): - key = load_ssh_public_key(key) - else: - key = load_pem_private_key(key, password=None) - except ValueError: - key = load_pem_public_key(key) - else: - raise TypeError("Expecting a PEM-formatted key.") - - return key - - @staticmethod - def to_jwk(key_obj): - obj = None - - if getattr(key_obj, "private_numbers", None): - # Private key - numbers = key_obj.private_numbers() - - obj = { - "kty": "RSA", - "key_ops": ["sign"], - "n": to_base64url_uint(numbers.public_numbers.n).decode(), - "e": to_base64url_uint(numbers.public_numbers.e).decode(), - "d": to_base64url_uint(numbers.d).decode(), - "p": to_base64url_uint(numbers.p).decode(), - "q": to_base64url_uint(numbers.q).decode(), - "dp": to_base64url_uint(numbers.dmp1).decode(), - "dq": to_base64url_uint(numbers.dmq1).decode(), - "qi": to_base64url_uint(numbers.iqmp).decode(), - } - - elif getattr(key_obj, "verify", None): - # Public key - numbers = key_obj.public_numbers() - - obj = { - "kty": "RSA", - "key_ops": ["verify"], - "n": to_base64url_uint(numbers.n).decode(), - "e": to_base64url_uint(numbers.e).decode(), - } - else: - raise InvalidKeyError("Not a public or private key") - - return json.dumps(obj) - - @staticmethod - def from_jwk(jwk): - try: - if isinstance(jwk, str): - obj = json.loads(jwk) - elif isinstance(jwk, dict): - obj = jwk - else: - raise ValueError - except ValueError: - raise InvalidKeyError("Key is not valid JSON") - - if obj.get("kty") != "RSA": - raise InvalidKeyError("Not an RSA key") - - if "d" in obj and "e" in obj and "n" in obj: - # Private key - if "oth" in obj: - raise InvalidKeyError( - "Unsupported RSA private key: > 2 primes not supported" - ) - - other_props = ["p", "q", "dp", "dq", "qi"] - props_found = [prop in obj for prop in other_props] - any_props_found = any(props_found) - - if any_props_found and not all(props_found): - raise InvalidKeyError( - "RSA key must include all parameters if any are present besides d" - ) - - public_numbers = RSAPublicNumbers( - from_base64url_uint(obj["e"]), - from_base64url_uint(obj["n"]), - ) - - if any_props_found: - numbers = RSAPrivateNumbers( - d=from_base64url_uint(obj["d"]), - p=from_base64url_uint(obj["p"]), - q=from_base64url_uint(obj["q"]), - dmp1=from_base64url_uint(obj["dp"]), - dmq1=from_base64url_uint(obj["dq"]), - iqmp=from_base64url_uint(obj["qi"]), - public_numbers=public_numbers, - ) - else: - d = from_base64url_uint(obj["d"]) - p, q = rsa_recover_prime_factors( - public_numbers.n, d, public_numbers.e - ) - - numbers = RSAPrivateNumbers( - d=d, - p=p, - q=q, - dmp1=rsa_crt_dmp1(d, p), - dmq1=rsa_crt_dmq1(d, q), - iqmp=rsa_crt_iqmp(p, q), - public_numbers=public_numbers, - ) - - return numbers.private_key() - elif "n" in obj and "e" in obj: - # Public key - numbers = RSAPublicNumbers( - from_base64url_uint(obj["e"]), - from_base64url_uint(obj["n"]), - ) - - return numbers.public_key() - else: - raise InvalidKeyError("Not a public or private key") - - def sign(self, msg, key): - return key.sign(msg, padding.PKCS1v15(), self.hash_alg()) - - def verify(self, msg, key, sig): - try: - key.verify(sig, msg, padding.PKCS1v15(), self.hash_alg()) - return True - except InvalidSignature: - return False - - class ECAlgorithm(Algorithm): - """ - Performs signing and verification operations using - ECDSA and the specified hash function - """ - - SHA256 = hashes.SHA256 - SHA384 = hashes.SHA384 - SHA512 = hashes.SHA512 - - def __init__(self, hash_alg): - self.hash_alg = hash_alg - - def prepare_key(self, key): - if isinstance(key, EllipticCurvePrivateKey) or isinstance( - key, EllipticCurvePublicKey - ): - return key - - if isinstance(key, (bytes, str)): - key = force_bytes(key) - - # Attempt to load key. We don't know if it's - # a Signing Key or a Verifying Key, so we try - # the Verifying Key first. - try: - if key.startswith(b"ecdsa-sha2-"): - key = load_ssh_public_key(key) - else: - key = load_pem_public_key(key) - except ValueError: - key = load_pem_private_key(key, password=None) - - else: - raise TypeError("Expecting a PEM-formatted key.") - - return key - - def sign(self, msg, key): - der_sig = key.sign(msg, ec.ECDSA(self.hash_alg())) - - return der_to_raw_signature(der_sig, key.curve) - - def verify(self, msg, key, sig): - try: - der_sig = raw_to_der_signature(sig, key.curve) - except ValueError: - return False - - try: - key.verify(der_sig, msg, ec.ECDSA(self.hash_alg())) - return True - except InvalidSignature: - return False - - @staticmethod - def from_jwk(jwk): - try: - if isinstance(jwk, str): - obj = json.loads(jwk) - elif isinstance(jwk, dict): - obj = jwk - else: - raise ValueError - except ValueError: - raise InvalidKeyError("Key is not valid JSON") - - if obj.get("kty") != "EC": - raise InvalidKeyError("Not an Elliptic curve key") - - if "x" not in obj or "y" not in obj: - raise InvalidKeyError("Not an Elliptic curve key") - - x = base64url_decode(obj.get("x")) - y = base64url_decode(obj.get("y")) - - curve = obj.get("crv") - if curve == "P-256": - if len(x) == len(y) == 32: - curve_obj = ec.SECP256R1() - else: - raise InvalidKeyError("Coords should be 32 bytes for curve P-256") - elif curve == "P-384": - if len(x) == len(y) == 48: - curve_obj = ec.SECP384R1() - else: - raise InvalidKeyError("Coords should be 48 bytes for curve P-384") - elif curve == "P-521": - if len(x) == len(y) == 66: - curve_obj = ec.SECP521R1() - else: - raise InvalidKeyError("Coords should be 66 bytes for curve P-521") - elif curve == "secp256k1": - if len(x) == len(y) == 32: - curve_obj = ec.SECP256K1() - else: - raise InvalidKeyError( - "Coords should be 32 bytes for curve secp256k1" - ) - else: - raise InvalidKeyError(f"Invalid curve: {curve}") - - public_numbers = ec.EllipticCurvePublicNumbers( - x=int.from_bytes(x, byteorder="big"), - y=int.from_bytes(y, byteorder="big"), - curve=curve_obj, - ) - - if "d" not in obj: - return public_numbers.public_key() - - d = base64url_decode(obj.get("d")) - if len(d) != len(x): - raise InvalidKeyError( - "D should be {} bytes for curve {}", len(x), curve - ) - - return ec.EllipticCurvePrivateNumbers( - int.from_bytes(d, byteorder="big"), public_numbers - ).private_key() - - class RSAPSSAlgorithm(RSAAlgorithm): - """ - Performs a signature using RSASSA-PSS with MGF1 - """ - - def sign(self, msg, key): - return key.sign( - msg, - padding.PSS( - mgf=padding.MGF1(self.hash_alg()), - salt_length=self.hash_alg.digest_size, - ), - self.hash_alg(), - ) - - def verify(self, msg, key, sig): - try: - key.verify( - sig, - msg, - padding.PSS( - mgf=padding.MGF1(self.hash_alg()), - salt_length=self.hash_alg.digest_size, - ), - self.hash_alg(), - ) - return True - except InvalidSignature: - return False - - class Ed25519Algorithm(Algorithm): - """ - Performs signing and verification operations using Ed25519 - - This class requires ``cryptography>=2.6`` to be installed. - """ - - def __init__(self, **kwargs): - pass - - def prepare_key(self, key): - - if isinstance(key, (Ed25519PrivateKey, Ed25519PublicKey)): - return key - - if isinstance(key, (bytes, str)): - if isinstance(key, str): - key = key.encode("utf-8") - str_key = key.decode("utf-8") - - if "-----BEGIN PUBLIC" in str_key: - return load_pem_public_key(key) - if "-----BEGIN PRIVATE" in str_key: - return load_pem_private_key(key, password=None) - if str_key[0:4] == "ssh-": - return load_ssh_public_key(key) - - raise TypeError("Expecting a PEM-formatted or OpenSSH key.") - - def sign(self, msg, key): - """ - Sign a message ``msg`` using the Ed25519 private key ``key`` - :param str|bytes msg: Message to sign - :param Ed25519PrivateKey key: A :class:`.Ed25519PrivateKey` instance - :return bytes signature: The signature, as bytes - """ - msg = bytes(msg, "utf-8") if type(msg) is not bytes else msg - return key.sign(msg) - - def verify(self, msg, key, sig): - """ - Verify a given ``msg`` against a signature ``sig`` using the Ed25519 key ``key`` - - :param str|bytes sig: Ed25519 signature to check ``msg`` against - :param str|bytes msg: Message to sign - :param Ed25519PrivateKey|Ed25519PublicKey key: A private or public Ed25519 key instance - :return bool verified: True if signature is valid, False if not. - """ - try: - msg = bytes(msg, "utf-8") if type(msg) is not bytes else msg - sig = bytes(sig, "utf-8") if type(sig) is not bytes else sig - - if isinstance(key, Ed25519PrivateKey): - key = key.public_key() - key.verify(sig, msg) - return True # If no exception was raised, the signature is valid. - except cryptography.exceptions.InvalidSignature: - return False - - @staticmethod - def from_jwk(jwk): - try: - if isinstance(jwk, str): - obj = json.loads(jwk) - elif isinstance(jwk, dict): - obj = jwk - else: - raise ValueError - except ValueError: - raise InvalidKeyError("Key is not valid JSON") - - if obj.get("kty") != "OKP": - raise InvalidKeyError("Not an Octet Key Pair") - - curve = obj.get("crv") - if curve != "Ed25519": - raise InvalidKeyError(f"Invalid curve: {curve}") - - if "x" not in obj: - raise InvalidKeyError('OKP should have "x" parameter') - x = base64url_decode(obj.get("x")) - - try: - if "d" not in obj: - return Ed25519PublicKey.from_public_bytes(x) - d = base64url_decode(obj.get("d")) - return Ed25519PrivateKey.from_private_bytes(d) - except ValueError as err: - raise InvalidKeyError("Invalid key parameter") from err diff --git a/jans-cli/cli/pylib/jwt/api_jwk.py b/jans-cli/cli/pylib/jwt/api_jwk.py deleted file mode 100644 index a0f6364da0a..00000000000 --- a/jans-cli/cli/pylib/jwt/api_jwk.py +++ /dev/null @@ -1,97 +0,0 @@ -import json - -from .algorithms import get_default_algorithms -from .exceptions import InvalidKeyError, PyJWKError, PyJWKSetError - - -class PyJWK: - def __init__(self, jwk_data, algorithm=None): - self._algorithms = get_default_algorithms() - self._jwk_data = jwk_data - - kty = self._jwk_data.get("kty", None) - if not kty: - raise InvalidKeyError("kty is not found: %s" % self._jwk_data) - - if not algorithm and isinstance(self._jwk_data, dict): - algorithm = self._jwk_data.get("alg", None) - - if not algorithm: - # Determine alg with kty (and crv). - crv = self._jwk_data.get("crv", None) - if kty == "EC": - if crv == "P-256" or not crv: - algorithm = "ES256" - elif crv == "P-384": - algorithm = "ES384" - elif crv == "P-521": - algorithm = "ES512" - elif crv == "secp256k1": - algorithm = "ES256K" - else: - raise InvalidKeyError("Unsupported crv: %s" % crv) - elif kty == "RSA": - algorithm = "RS256" - elif kty == "oct": - algorithm = "HS256" - elif kty == "OKP": - if not crv: - raise InvalidKeyError("crv is not found: %s" % self._jwk_data) - if crv == "Ed25519": - algorithm = "EdDSA" - else: - raise InvalidKeyError("Unsupported crv: %s" % crv) - else: - raise InvalidKeyError("Unsupported kty: %s" % kty) - - self.Algorithm = self._algorithms.get(algorithm) - - if not self.Algorithm: - raise PyJWKError("Unable to find a algorithm for key: %s" % self._jwk_data) - - self.key = self.Algorithm.from_jwk(self._jwk_data) - - @staticmethod - def from_dict(obj, algorithm=None): - return PyJWK(obj, algorithm) - - @staticmethod - def from_json(data, algorithm=None): - obj = json.loads(data) - return PyJWK.from_dict(obj, algorithm) - - @property - def key_type(self): - return self._jwk_data.get("kty", None) - - @property - def key_id(self): - return self._jwk_data.get("kid", None) - - @property - def public_key_use(self): - return self._jwk_data.get("use", None) - - -class PyJWKSet: - def __init__(self, keys): - self.keys = [] - - if not keys or not isinstance(keys, list): - raise PyJWKSetError("Invalid JWK Set value") - - if len(keys) == 0: - raise PyJWKSetError("The JWK Set did not contain any keys") - - for key in keys: - self.keys.append(PyJWK(key)) - - @staticmethod - def from_dict(obj): - keys = obj.get("keys", []) - return PyJWKSet(keys) - - @staticmethod - def from_json(data): - obj = json.loads(data) - return PyJWKSet.from_dict(obj) diff --git a/jans-cli/cli/pylib/jwt/api_jws.py b/jans-cli/cli/pylib/jwt/api_jws.py deleted file mode 100644 index 6d136999714..00000000000 --- a/jans-cli/cli/pylib/jwt/api_jws.py +++ /dev/null @@ -1,256 +0,0 @@ -import binascii -import json -from collections.abc import Mapping -from typing import Any, Dict, List, Optional, Type - -from .algorithms import ( - Algorithm, - get_default_algorithms, - has_crypto, - requires_cryptography, -) -from .exceptions import ( - DecodeError, - InvalidAlgorithmError, - InvalidSignatureError, - InvalidTokenError, -) -from .utils import base64url_decode, base64url_encode - - -class PyJWS: - header_typ = "JWT" - - def __init__(self, algorithms=None, options=None): - self._algorithms = get_default_algorithms() - self._valid_algs = ( - set(algorithms) if algorithms is not None else set(self._algorithms) - ) - - # Remove algorithms that aren't on the whitelist - for key in list(self._algorithms.keys()): - if key not in self._valid_algs: - del self._algorithms[key] - - if options is None: - options = {} - self.options = {**self._get_default_options(), **options} - - @staticmethod - def _get_default_options(): - return {"verify_signature": True} - - def register_algorithm(self, alg_id, alg_obj): - """ - Registers a new Algorithm for use when creating and verifying tokens. - """ - if alg_id in self._algorithms: - raise ValueError("Algorithm already has a handler.") - - if not isinstance(alg_obj, Algorithm): - raise TypeError("Object is not of type `Algorithm`") - - self._algorithms[alg_id] = alg_obj - self._valid_algs.add(alg_id) - - def unregister_algorithm(self, alg_id): - """ - Unregisters an Algorithm for use when creating and verifying tokens - Throws KeyError if algorithm is not registered. - """ - if alg_id not in self._algorithms: - raise KeyError( - "The specified algorithm could not be removed" - " because it is not registered." - ) - - del self._algorithms[alg_id] - self._valid_algs.remove(alg_id) - - def get_algorithms(self): - """ - Returns a list of supported values for the 'alg' parameter. - """ - return list(self._valid_algs) - - def encode( - self, - payload: bytes, - key: str, - algorithm: str = "HS256", - headers: Optional[Dict] = None, - json_encoder: Optional[Type[json.JSONEncoder]] = None, - ) -> str: - segments = [] - - if algorithm is None: - algorithm = "none" - - if algorithm not in self._valid_algs: - pass - - # Header - header = {"typ": self.header_typ, "alg": algorithm} - - if headers: - self._validate_headers(headers) - header.update(headers) - - json_header = json.dumps( - header, separators=(",", ":"), cls=json_encoder - ).encode() - - segments.append(base64url_encode(json_header)) - segments.append(base64url_encode(payload)) - - # Segments - signing_input = b".".join(segments) - try: - alg_obj = self._algorithms[algorithm] - key = alg_obj.prepare_key(key) - signature = alg_obj.sign(signing_input, key) - - except KeyError: - if not has_crypto and algorithm in requires_cryptography: - raise NotImplementedError( - "Algorithm '%s' could not be found. Do you have cryptography " - "installed?" % algorithm - ) - else: - raise NotImplementedError("Algorithm not supported") - - segments.append(base64url_encode(signature)) - - encoded_string = b".".join(segments) - - return encoded_string.decode("utf-8") - - def decode_complete( - self, - jwt: str, - key: str = "", - algorithms: List[str] = None, - options: Dict = None, - **kwargs, - ) -> Dict[str, Any]: - if options is None: - options = {} - merged_options = {**self.options, **options} - verify_signature = merged_options["verify_signature"] - - if verify_signature and not algorithms: - raise DecodeError( - 'It is required that you pass in a value for the "algorithms" argument when calling decode().' - ) - - payload, signing_input, header, signature = self._load(jwt) - - if verify_signature: - self._verify_signature(signing_input, header, signature, key, algorithms) - - return { - "payload": payload, - "header": header, - "signature": signature, - } - - def decode( - self, - jwt: str, - key: str = "", - algorithms: List[str] = None, - options: Dict = None, - **kwargs, - ) -> str: - decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs) - return decoded["payload"] - - def get_unverified_header(self, jwt): - """Returns back the JWT header parameters as a dict() - - Note: The signature is not verified so the header parameters - should not be fully trusted until signature verification is complete - """ - headers = self._load(jwt)[2] - self._validate_headers(headers) - - return headers - - def _load(self, jwt): - if isinstance(jwt, str): - jwt = jwt.encode("utf-8") - - if not isinstance(jwt, bytes): - raise DecodeError(f"Invalid token type. Token must be a {bytes}") - - try: - signing_input, crypto_segment = jwt.rsplit(b".", 1) - header_segment, payload_segment = signing_input.split(b".", 1) - except ValueError as err: - raise DecodeError("Not enough segments") from err - - try: - header_data = base64url_decode(header_segment) - except (TypeError, binascii.Error) as err: - raise DecodeError("Invalid header padding") from err - - try: - header = json.loads(header_data) - except ValueError as e: - raise DecodeError("Invalid header string: %s" % e) from e - - if not isinstance(header, Mapping): - raise DecodeError("Invalid header string: must be a json object") - - try: - payload = base64url_decode(payload_segment) - except (TypeError, binascii.Error) as err: - raise DecodeError("Invalid payload padding") from err - - try: - signature = base64url_decode(crypto_segment) - except (TypeError, binascii.Error) as err: - raise DecodeError("Invalid crypto padding") from err - - return (payload, signing_input, header, signature) - - def _verify_signature( - self, - signing_input, - header, - signature, - key="", - algorithms=None, - ): - - alg = header.get("alg") - - if algorithms is not None and alg not in algorithms: - raise InvalidAlgorithmError("The specified alg value is not allowed") - - try: - alg_obj = self._algorithms[alg] - key = alg_obj.prepare_key(key) - - if not alg_obj.verify(signing_input, key, signature): - raise InvalidSignatureError("Signature verification failed") - - except KeyError: - raise InvalidAlgorithmError("Algorithm not supported") - - def _validate_headers(self, headers): - if "kid" in headers: - self._validate_kid(headers["kid"]) - - def _validate_kid(self, kid): - if not isinstance(kid, str): - raise InvalidTokenError("Key ID header parameter must be a string") - - -_jws_global_obj = PyJWS() -encode = _jws_global_obj.encode -decode_complete = _jws_global_obj.decode_complete -decode = _jws_global_obj.decode -register_algorithm = _jws_global_obj.register_algorithm -unregister_algorithm = _jws_global_obj.unregister_algorithm -get_unverified_header = _jws_global_obj.get_unverified_header diff --git a/jans-cli/cli/pylib/jwt/api_jwt.py b/jans-cli/cli/pylib/jwt/api_jwt.py deleted file mode 100644 index 70a5e537dc1..00000000000 --- a/jans-cli/cli/pylib/jwt/api_jwt.py +++ /dev/null @@ -1,222 +0,0 @@ -import json -from calendar import timegm -from collections.abc import Iterable, Mapping -from datetime import datetime, timedelta -from typing import Any, Dict, List, Optional, Type, Union - -from . import api_jws -from .exceptions import ( - DecodeError, - ExpiredSignatureError, - ImmatureSignatureError, - InvalidAudienceError, - InvalidIssuedAtError, - InvalidIssuerError, - MissingRequiredClaimError, -) - - -class PyJWT: - def __init__(self, options=None): - if options is None: - options = {} - self.options = {**self._get_default_options(), **options} - - @staticmethod - def _get_default_options() -> Dict[str, Union[bool, List[str]]]: - return { - "verify_signature": True, - "verify_exp": True, - "verify_nbf": True, - "verify_iat": True, - "verify_aud": True, - "verify_iss": True, - "require": [], - } - - def encode( - self, - payload: Dict[str, Any], - key: str, - algorithm: str = "HS256", - headers: Optional[Dict] = None, - json_encoder: Optional[Type[json.JSONEncoder]] = None, - ) -> str: - # Check that we get a mapping - if not isinstance(payload, Mapping): - raise TypeError( - "Expecting a mapping object, as JWT only supports " - "JSON objects as payloads." - ) - - # Payload - payload = payload.copy() - for time_claim in ["exp", "iat", "nbf"]: - # Convert datetime to a intDate value in known time-format claims - if isinstance(payload.get(time_claim), datetime): - payload[time_claim] = timegm(payload[time_claim].utctimetuple()) - - json_payload = json.dumps( - payload, separators=(",", ":"), cls=json_encoder - ).encode("utf-8") - - return api_jws.encode(json_payload, key, algorithm, headers, json_encoder) - - def decode_complete( - self, - jwt: str, - key: str = "", - algorithms: List[str] = None, - options: Dict = None, - **kwargs, - ) -> Dict[str, Any]: - if options is None: - options = {"verify_signature": True} - else: - options.setdefault("verify_signature", True) - - if not options["verify_signature"]: - options.setdefault("verify_exp", False) - options.setdefault("verify_nbf", False) - options.setdefault("verify_iat", False) - options.setdefault("verify_aud", False) - options.setdefault("verify_iss", False) - - if options["verify_signature"] and not algorithms: - raise DecodeError( - 'It is required that you pass in a value for the "algorithms" argument when calling decode().' - ) - - decoded = api_jws.decode_complete( - jwt, - key=key, - algorithms=algorithms, - options=options, - **kwargs, - ) - - try: - payload = json.loads(decoded["payload"]) - except ValueError as e: - raise DecodeError("Invalid payload string: %s" % e) - if not isinstance(payload, dict): - raise DecodeError("Invalid payload string: must be a json object") - - merged_options = {**self.options, **options} - self._validate_claims(payload, merged_options, **kwargs) - - decoded["payload"] = payload - return decoded - - def decode( - self, - jwt: str, - key: str = "", - algorithms: List[str] = None, - options: Dict = None, - **kwargs, - ) -> Dict[str, Any]: - decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs) - return decoded["payload"] - - def _validate_claims( - self, payload, options, audience=None, issuer=None, leeway=0, **kwargs - ): - if isinstance(leeway, timedelta): - leeway = leeway.total_seconds() - - if not isinstance(audience, (bytes, str, type(None), Iterable)): - raise TypeError("audience must be a string, iterable, or None") - - self._validate_required_claims(payload, options) - - now = timegm(datetime.utcnow().utctimetuple()) - - if "iat" in payload and options["verify_iat"]: - self._validate_iat(payload, now, leeway) - - if "nbf" in payload and options["verify_nbf"]: - self._validate_nbf(payload, now, leeway) - - if "exp" in payload and options["verify_exp"]: - self._validate_exp(payload, now, leeway) - - if options["verify_iss"]: - self._validate_iss(payload, issuer) - - if options["verify_aud"]: - self._validate_aud(payload, audience) - - def _validate_required_claims(self, payload, options): - for claim in options["require"]: - if payload.get(claim) is None: - raise MissingRequiredClaimError(claim) - - def _validate_iat(self, payload, now, leeway): - try: - int(payload["iat"]) - except ValueError: - raise InvalidIssuedAtError("Issued At claim (iat) must be an integer.") - - def _validate_nbf(self, payload, now, leeway): - try: - nbf = int(payload["nbf"]) - except ValueError: - raise DecodeError("Not Before claim (nbf) must be an integer.") - - if nbf > (now + leeway): - raise ImmatureSignatureError("The token is not yet valid (nbf)") - - def _validate_exp(self, payload, now, leeway): - try: - exp = int(payload["exp"]) - except ValueError: - raise DecodeError("Expiration Time claim (exp) must be an" " integer.") - - if exp < (now - leeway): - raise ExpiredSignatureError("Signature has expired") - - def _validate_aud(self, payload, audience): - if audience is None and "aud" not in payload: - return - - if audience is not None and "aud" not in payload: - # Application specified an audience, but it could not be - # verified since the token does not contain a claim. - raise MissingRequiredClaimError("aud") - - if audience is None and "aud" in payload: - # Application did not specify an audience, but - # the token has the 'aud' claim - raise InvalidAudienceError("Invalid audience") - - audience_claims = payload["aud"] - - if isinstance(audience_claims, str): - audience_claims = [audience_claims] - if not isinstance(audience_claims, list): - raise InvalidAudienceError("Invalid claim format in token") - if any(not isinstance(c, str) for c in audience_claims): - raise InvalidAudienceError("Invalid claim format in token") - - if isinstance(audience, str): - audience = [audience] - - if not any(aud in audience_claims for aud in audience): - raise InvalidAudienceError("Invalid audience") - - def _validate_iss(self, payload, issuer): - if issuer is None: - return - - if "iss" not in payload: - raise MissingRequiredClaimError("iss") - - if payload["iss"] != issuer: - raise InvalidIssuerError("Invalid issuer") - - -_jwt_global_obj = PyJWT() -encode = _jwt_global_obj.encode -decode_complete = _jwt_global_obj.decode_complete -decode = _jwt_global_obj.decode diff --git a/jans-cli/cli/pylib/jwt/exceptions.py b/jans-cli/cli/pylib/jwt/exceptions.py deleted file mode 100644 index 308899aa6a6..00000000000 --- a/jans-cli/cli/pylib/jwt/exceptions.py +++ /dev/null @@ -1,66 +0,0 @@ -class PyJWTError(Exception): - """ - Base class for all exceptions - """ - - pass - - -class InvalidTokenError(PyJWTError): - pass - - -class DecodeError(InvalidTokenError): - pass - - -class InvalidSignatureError(DecodeError): - pass - - -class ExpiredSignatureError(InvalidTokenError): - pass - - -class InvalidAudienceError(InvalidTokenError): - pass - - -class InvalidIssuerError(InvalidTokenError): - pass - - -class InvalidIssuedAtError(InvalidTokenError): - pass - - -class ImmatureSignatureError(InvalidTokenError): - pass - - -class InvalidKeyError(PyJWTError): - pass - - -class InvalidAlgorithmError(InvalidTokenError): - pass - - -class MissingRequiredClaimError(InvalidTokenError): - def __init__(self, claim): - self.claim = claim - - def __str__(self): - return 'Token is missing the "%s" claim' % self.claim - - -class PyJWKError(PyJWTError): - pass - - -class PyJWKSetError(PyJWTError): - pass - - -class PyJWKClientError(PyJWTError): - pass diff --git a/jans-cli/cli/pylib/jwt/help.py b/jans-cli/cli/pylib/jwt/help.py deleted file mode 100644 index d8f23024216..00000000000 --- a/jans-cli/cli/pylib/jwt/help.py +++ /dev/null @@ -1,60 +0,0 @@ -import json -import platform -import sys - -from . import __version__ as pyjwt_version - -try: - import cryptography -except ModuleNotFoundError: - cryptography = None # type: ignore - - -def info(): - """ - Generate information for a bug report. - Based on the requests package help utility module. - """ - try: - platform_info = { - "system": platform.system(), - "release": platform.release(), - } - except OSError: - platform_info = {"system": "Unknown", "release": "Unknown"} - - implementation = platform.python_implementation() - - if implementation == "CPython": - implementation_version = platform.python_version() - elif implementation == "PyPy": - implementation_version = "{}.{}.{}".format( - sys.pypy_version_info.major, - sys.pypy_version_info.minor, - sys.pypy_version_info.micro, - ) - if sys.pypy_version_info.releaselevel != "final": - implementation_version = "".join( - [implementation_version, sys.pypy_version_info.releaselevel] - ) - else: - implementation_version = "Unknown" - - return { - "platform": platform_info, - "implementation": { - "name": implementation, - "version": implementation_version, - }, - "cryptography": {"version": getattr(cryptography, "__version__", "")}, - "pyjwt": {"version": pyjwt_version}, - } - - -def main(): - """Pretty-print the bug information as JSON.""" - print(json.dumps(info(), sort_keys=True, indent=2)) - - -if __name__ == "__main__": - main() diff --git a/jans-cli/cli/pylib/jwt/jwks_client.py b/jans-cli/cli/pylib/jwt/jwks_client.py deleted file mode 100644 index dc86c3be336..00000000000 --- a/jans-cli/cli/pylib/jwt/jwks_client.py +++ /dev/null @@ -1,59 +0,0 @@ -import json -import urllib.request -from functools import lru_cache -from typing import Any, List - -from .api_jwk import PyJWK, PyJWKSet -from .api_jwt import decode_complete as decode_token -from .exceptions import PyJWKClientError - - -class PyJWKClient: - def __init__(self, uri: str, cache_keys: bool = True, max_cached_keys: int = 16): - self.uri = uri - if cache_keys: - # Cache signing keys - # Ignore mypy (https://github.com/python/mypy/issues/2427) - self.get_signing_key = lru_cache(maxsize=max_cached_keys)(self.get_signing_key) # type: ignore - - def fetch_data(self) -> Any: - with urllib.request.urlopen(self.uri) as response: - return json.load(response) - - def get_jwk_set(self) -> PyJWKSet: - data = self.fetch_data() - return PyJWKSet.from_dict(data) - - def get_signing_keys(self) -> List[PyJWK]: - jwk_set = self.get_jwk_set() - signing_keys = [] - - for jwk_set_key in jwk_set.keys: - if jwk_set_key.public_key_use == "sig" and jwk_set_key.key_id: - signing_keys.append(jwk_set_key) - - if len(signing_keys) == 0: - raise PyJWKClientError("The JWKS endpoint did not contain any signing keys") - - return signing_keys - - def get_signing_key(self, kid: str) -> PyJWK: - signing_keys = self.get_signing_keys() - signing_key = None - - for key in signing_keys: - if key.key_id == kid: - signing_key = key - break - - if not signing_key: - raise PyJWKClientError( - f'Unable to find a signing key that matches: "{kid}"' - ) - - return signing_key - - def get_signing_key_from_jwt(self, token: str) -> PyJWK: - unverified = decode_token(token, options={"verify_signature": False}) - header = unverified["header"] - return self.get_signing_key(header.get("kid")) diff --git a/jans-cli/cli/pylib/jwt/py.typed b/jans-cli/cli/pylib/jwt/py.typed deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/jans-cli/cli/pylib/jwt/utils.py b/jans-cli/cli/pylib/jwt/utils.py deleted file mode 100644 index fefadbcdfbd..00000000000 --- a/jans-cli/cli/pylib/jwt/utils.py +++ /dev/null @@ -1,100 +0,0 @@ -import base64 -import binascii -from typing import Any, Union - -try: - from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurve - from cryptography.hazmat.primitives.asymmetric.utils import ( - decode_dss_signature, - encode_dss_signature, - ) -except ModuleNotFoundError: - EllipticCurve = Any # type: ignore - - -def force_bytes(value: Union[str, bytes]) -> bytes: - if isinstance(value, str): - return value.encode("utf-8") - elif isinstance(value, bytes): - return value - else: - raise TypeError("Expected a string value") - - -def base64url_decode(input: Union[str, bytes]) -> bytes: - if isinstance(input, str): - input = input.encode("ascii") - - rem = len(input) % 4 - - if rem > 0: - input += b"=" * (4 - rem) - - return base64.urlsafe_b64decode(input) - - -def base64url_encode(input: bytes) -> bytes: - return base64.urlsafe_b64encode(input).replace(b"=", b"") - - -def to_base64url_uint(val: int) -> bytes: - if val < 0: - raise ValueError("Must be a positive integer") - - int_bytes = bytes_from_int(val) - - if len(int_bytes) == 0: - int_bytes = b"\x00" - - return base64url_encode(int_bytes) - - -def from_base64url_uint(val: Union[str, bytes]) -> int: - if isinstance(val, str): - val = val.encode("ascii") - - data = base64url_decode(val) - return int.from_bytes(data, byteorder="big") - - -def number_to_bytes(num: int, num_bytes: int) -> bytes: - padded_hex = "%0*x" % (2 * num_bytes, num) - big_endian = binascii.a2b_hex(padded_hex.encode("ascii")) - return big_endian - - -def bytes_to_number(string: bytes) -> int: - return int(binascii.b2a_hex(string), 16) - - -def bytes_from_int(val: int) -> bytes: - remaining = val - byte_length = 0 - - while remaining != 0: - remaining = remaining >> 8 - byte_length += 1 - - return val.to_bytes(byte_length, "big", signed=False) - - -def der_to_raw_signature(der_sig: bytes, curve: EllipticCurve) -> bytes: - num_bits = curve.key_size - num_bytes = (num_bits + 7) // 8 - - r, s = decode_dss_signature(der_sig) - - return number_to_bytes(r, num_bytes) + number_to_bytes(s, num_bytes) - - -def raw_to_der_signature(raw_sig: bytes, curve: EllipticCurve) -> bytes: - num_bits = curve.key_size - num_bytes = (num_bits + 7) // 8 - - if len(raw_sig) != 2 * num_bytes: - raise ValueError("Invalid signature") - - r = bytes_to_number(raw_sig[:num_bytes]) - s = bytes_to_number(raw_sig[num_bytes:]) - - return encode_dss_signature(r, s)