From commits-return-21481-archive-asf-public=cust-asf.ponee.io@airavata.apache.org Fri Oct 11 20:42:13 2019 Return-Path: X-Original-To: archive-asf-public@cust-asf.ponee.io Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [207.244.88.153]) by mx-eu-01.ponee.io (Postfix) with SMTP id 92D1E180667 for ; Fri, 11 Oct 2019 22:42:12 +0200 (CEST) Received: (qmail 73988 invoked by uid 500); 11 Oct 2019 20:42:09 -0000 Mailing-List: contact commits-help@airavata.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@airavata.apache.org Delivered-To: mailing list commits@airavata.apache.org Received: (qmail 73372 invoked by uid 99); 11 Oct 2019 20:42:08 -0000 Received: from ec2-52-202-80-70.compute-1.amazonaws.com (HELO gitbox.apache.org) (52.202.80.70) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 11 Oct 2019 20:42:08 +0000 Received: by gitbox.apache.org (ASF Mail Server at gitbox.apache.org, from userid 33) id 08E2D811D9; Fri, 11 Oct 2019 20:42:08 +0000 (UTC) Date: Fri, 11 Oct 2019 20:42:25 +0000 To: "commits@airavata.apache.org" Subject: [airavata-custos] 18/24: added the functionality to load server configuration from ini file MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit From: machristie@apache.org In-Reply-To: <157082652754.4215.9107757484496232998@gitbox.apache.org> References: <157082652754.4215.9107757484496232998@gitbox.apache.org> X-Git-Host: gitbox.apache.org X-Git-Repo: airavata-custos X-Git-Refname: refs/heads/master X-Git-Reftype: branch X-Git-Rev: 7d3e8c8c18916e28d1776bf4a1ed470ad3ee34f2 X-Git-NotificationType: diff X-Git-Multimail-Version: 1.5.dev Auto-Submitted: auto-generated Message-Id: <20191011204208.08E2D811D9@gitbox.apache.org> This is an automated email from the ASF dual-hosted git repository. machristie pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/airavata-custos.git commit 7d3e8c8c18916e28d1776bf4a1ed470ad3ee34f2 Author: Aarushi AuthorDate: Tue Sep 17 12:22:01 2019 -0400 added the functionality to load server configuration from ini file --- .../airavata_custos/admin/iam_admin_client.py | 244 +++++++++++---------- clients/python/airavata_custos/sample_settings.ini | 10 + .../security/custos_authorization_token.py | 43 ---- .../security/keycloak_connectors.py | 78 +++++-- clients/python/airavata_custos/settings.py | 30 ++- clients/python/airavata_custos/utils.py | 20 +- clients/python/requirements_dev.txt | 3 +- 7 files changed, 228 insertions(+), 200 deletions(-) diff --git a/clients/python/airavata_custos/admin/iam_admin_client.py b/clients/python/airavata_custos/admin/iam_admin_client.py index ee9c11b..f72df76 100644 --- a/clients/python/airavata_custos/admin/iam_admin_client.py +++ b/clients/python/airavata_custos/admin/iam_admin_client.py @@ -16,123 +16,135 @@ # import logging -from airavata_custos.utils import iamadmin_client_pool +import configparser +from airavata_custos import utils +from airavata_custos.settings import ProfileSettings logger = logging.getLogger(__name__) -def is_username_available(authz_token, username): - """ - This method validates if the username is available or not - :param authz_token: Object of AuthzToken class containing access token, username, gatewayId of the active user - :param username: The username whose availability needs to be verified - :return: boolean - """ - return iamadmin_client_pool.isUsernameAvailable(authz_token, username) - - -def register_user(authz_token, username, email_address, first_name, last_name, password): - """ - This method registers the user with the keycloak instance returns true if successful, false if the registration fails - :param authz_token: Object of AuthzToken class containing access token, username, gatewayId of the active user - :param username: The username of the user that needs to be registered - :param email_address: The email address of the user that needs to be registered - :param first_name: The first name of the user that needs to be registered - :param last_name: The last name of the user that needs to be registered - :param password: The password of the user that needs to be registered - :return: boolean - """ - return iamadmin_client_pool.registerUser( - authz_token, - username, - email_address, - first_name, - last_name, - password) - - -def is_user_enabled(authz_token, username): - """ - Checks the user is enabled/disabled in keycloak. Only the enabled user can login - :param authz_token: Object of AuthzToken class containing access token, username, gatewayId of the active user - :param username: The username of the user - :return: boolean - """ - return iamadmin_client_pool.isUserEnabled(authz_token, username) - - -def enable_user(authz_token, username): - """ - The method to enable a disabled user - :param authz_token: Object of AuthzToken class containing access token, username, gatewayId of the active user - :param username: The username of the user - :return: Object of UserProfile class, containing user details - """ - return iamadmin_client_pool.enableUser(authz_token, username) - - -def delete_user(authz_token, username): - """ - This method deleted the user from keycloak. Returns true if delete is successful - :param authz_token: Object of AuthzToken class containing access token, username, gatewayId of the active user - :param username: The username of the user - :return: boolean - """ - return iamadmin_client_pool.deleteUser(authz_token, username) - - -def is_user_exist(authz_token, username): - """ - This method checks if the user exists in keycloak. Returns true if the user exists otherwise returns false - :param authz_token: Object of AuthzToken class containing access token, username, gatewayId of the active user - :param username: The username of the user - :return: boolean - """ - try: - return iamadmin_client_pool.isUserExist(authz_token, username) - except Exception: - return None - - -def get_user(authz_token, username): - """ - - :param authz_token: Object of AuthzToken class containing access token, username, gatewayId of the active user - :param username: username of the user - :return: object of class UserProfile - """ - try: - return iamadmin_client_pool.getUser(authz_token, username) - except Exception: - return None - - -def get_users(authz_token, offset=0, limit=-1, search=None): - """ - - :param authz_token: Object of AuthzToken class containing access token, username, gatewayId of the active user - :param offset: start index - :param limit: end index - :param search: search criteria for filtering users - :return: list of UserProfile class objects - """ - try: - return iamadmin_client_pool.getUsers(authz_token, offset, limit, search) - except Exception: - return None - - -def reset_user_password(authz_token, username, new_password): - """ - - :param authz_token: Object of AuthzToken class containing access token, username, gatewayId of the active user - :param username: username of the user - :param new_password: new password for the user - :return: - """ - try: - return iamadmin_client_pool.resetUserPassword( - authz_token, username, new_password) - except Exception: - return None - +class IAMAdminClient(object): + + def __init__(self, configuration_file_location): + """ + constructor for IAMAdminClient class + :param configuration_file_location: takes the location of the ini file containing server configuration + """ + self.profile_settings = ProfileSettings() + self._load_settings(configuration_file_location) + self.iamadmin_client_pool = utils.initialize_iamadmin_client_pool(self.profile_settings.PROFILE_SERVICE_HOST, + self.profile_settings.PROFILE_SERVICE_PORT) + + def is_username_available(self, authz_token, username): + """ + This method validates if the username is available or not + :param authz_token: Object of AuthzToken class containing access token, username, gatewayId of the active user + :param username: The username whose availability needs to be verified + :return: boolean + """ + return self.iamadmin_client_pool.isUsernameAvailable(authz_token, username) + + def register_user(self, authz_token, username, email_address, first_name, last_name, password): + """ + This method registers the user with the keycloak instance returns true if successful, false if the registration fails + :param authz_token: Object of AuthzToken class containing access token, username, gatewayId of the active user + :param username: The username of the user that needs to be registered + :param email_address: The email address of the user that needs to be registered + :param first_name: The first name of the user that needs to be registered + :param last_name: The last name of the user that needs to be registered + :param password: The password of the user that needs to be registered + :return: boolean + """ + return self.iamadmin_client_pool.registerUser( + authz_token, + username, + email_address, + first_name, + last_name, + password) + + def is_user_enabled(self, authz_token, username): + """ + Checks the user is enabled/disabled in keycloak. Only the enabled user can login + :param authz_token: Object of AuthzToken class containing access token, username, gatewayId of the active user + :param username: The username of the user + :return: boolean + """ + return self.iamadmin_client_pool.isUserEnabled(authz_token, username) + + def enable_user(self, authz_token, username): + """ + The method to enable a disabled user + :param authz_token: Object of AuthzToken class containing access token, username, gatewayId of the active user + :param username: The username of the user + :return: Object of UserProfile class, containing user details + """ + return self.iamadmin_client_pool.enableUser(authz_token, username) + + def delete_user(self, authz_token, username): + """ + This method deleted the user from keycloak. Returns true if delete is successful + :param authz_token: Object of AuthzToken class containing access token, username, gatewayId of the active user + :param username: The username of the user + :return: boolean + """ + return self.iamadmin_client_pool.deleteUser(authz_token, username) + + def is_user_exist(self, authz_token, username): + """ + This method checks if the user exists in keycloak. Returns true if the user exists otherwise returns false + :param authz_token: Object of AuthzToken class containing access token, username, gatewayId of the active user + :param username: The username of the user + :return: boolean + """ + try: + return self.iamadmin_client_pool.isUserExist(authz_token, username) + except Exception: + return None + + def get_user(self, authz_token, username): + """ + + :param authz_token: Object of AuthzToken class containing access token, username, gatewayId of the active user + :param username: username of the user + :return: object of class UserProfile + """ + try: + return self.iamadmin_client_pool.getUser(authz_token, username) + except Exception: + return None + + def get_users(self, authz_token, offset=0, limit=-1, search=None): + """ + + :param authz_token: Object of AuthzToken class containing access token, username, gatewayId of the active user + :param offset: start index + :param limit: end index + :param search: search criteria for filtering users + :return: list of UserProfile class objects + """ + try: + return self.iamadmin_client_pool.getUsers(authz_token, offset, limit, search) + except Exception: + return None + + def reset_user_password(self, authz_token, username, new_password): + """ + + :param authz_token: Object of AuthzToken class containing access token, username, gatewayId of the active user + :param username: username of the user + :param new_password: new password for the user + :return: + """ + try: + return self.iamadmin_client_pool.resetUserPassword( + authz_token, username, new_password) + except Exception: + return None + + def _load_settings(self, configuration_file_location): + config = configparser.ConfigParser() + config.read(configuration_file_location) + settings = config['ProfileServerSettings'] + self.profile_settings.PROFILE_SERVICE_HOST = settings['PROFILE_SERVICE_HOST'] + self.profile_settings.PROFILE_SERVICE_PORT = settings['PROFILE_SERVICE_PORT'] diff --git a/clients/python/airavata_custos/sample_settings.ini b/clients/python/airavata_custos/sample_settings.ini new file mode 100644 index 0000000..c9a1e1c --- /dev/null +++ b/clients/python/airavata_custos/sample_settings.ini @@ -0,0 +1,10 @@ +[IAMSeverSettings] +KEYCLOAK_AUTHORIZE_URL = https://localhost:8443/auth/realms/default/protocol/openid-connect/auth +KEYCLOAK_TOKEN_URL = https://localhost:8443/auth/realms/default/protocol/openid-connect/token +KEYCLOAK_USERINFO_URL = https://localhost:8443/auth/realms/default/protocol/openid-connect/userinfo +KEYCLOAK_LOGOUT_URL = https://localhost:8443/auth/realms/default/protocol/openid-connect/logout +VERIFY_SSL = no + +[ProfileServerSettings] +PROFILE_SERVICE_HOST = '0.0.0.0' +PROFILE_SERVICE_PORT = '8081' \ No newline at end of file diff --git a/clients/python/airavata_custos/security/custos_authorization_token.py b/clients/python/airavata_custos/security/custos_authorization_token.py deleted file mode 100644 index dd4b531..0000000 --- a/clients/python/airavata_custos/security/custos_authorization_token.py +++ /dev/null @@ -1,43 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -from airavata_custos import settings -from oauthlib.oauth2 import BackendApplicationClient -from requests_oauthlib import OAuth2Session -from custos.commons.model.security.ttypes import AuthzToken - - -def get_authorization_token(client_credentials, tenant_id, username=None): - """ - This method created a authorization token for the user or a service account - In case of a service account username will be null - :param client_credentials: object of class client_credentials - :param tenant_id: gateway id of the client - :param username: username of the user for which authorization token is being created - :return: AuthzToken - """ - client = BackendApplicationClient(client_id=client_credentials.client_id) - oauth = OAuth2Session(client=client) - token = oauth.fetch_token( - token_url=settings.token_url, - client_id=client_credentials.client_id, - client_secret=client_credentials.client_secret, - verify=client_credentials.verify_ssl) - - access_token = token.get('access_token') - return AuthzToken( - accessToken=access_token, - claimsMap={'gatewayID': tenant_id, 'userName': username}) diff --git a/clients/python/airavata_custos/security/keycloak_connectors.py b/clients/python/airavata_custos/security/keycloak_connectors.py index d31e5a8..05816c2 100644 --- a/clients/python/airavata_custos/security/keycloak_connectors.py +++ b/clients/python/airavata_custos/security/keycloak_connectors.py @@ -16,13 +16,24 @@ # import time from oauthlib.oauth2 import LegacyApplicationClient -from requests_oauthlib import OAuth2Session import requests -from airavata_custos import settings +import configparser +from airavata_custos.settings import IAMSettings +from oauthlib.oauth2 import BackendApplicationClient +from requests_oauthlib import OAuth2Session +from custos.commons.model.security.ttypes import AuthzToken class KeycloakBackend(object): + def __init__(self, configuration_file_location): + """ + constructor for KeycloakBackend class + :param configuration_file_location: takes the location of the ini file containing server configuration + """ + self.keycloak_settings = IAMSettings() + self._load_settings(configuration_file_location) + def authenticate_user(self, user_credentials): """ Method to authenticate a gateway user with keycloak @@ -60,42 +71,61 @@ class KeycloakBackend(object): except Exception as e: return None - @classmethod - def _get_token_and_user_info_password_flow(cls, client_credentials): + def _get_token_and_user_info_password_flow(self, client_credentials): oauth2_session = OAuth2Session(client=LegacyApplicationClient(client_id=client_credentials.client_id)) - token = oauth2_session.fetch_token(token_url=settings.KEYCLOAK_TOKEN_URL, + token = oauth2_session.fetch_token(token_url=self.keycloak_settings.KEYCLOAK_TOKEN_URL, username=client_credentials.username, password=client_credentials.password, client_id=client_credentials.client_id, client_secret=client_credentials.client_secret, - verify=settings.VERIFY_SSL) - user_info = oauth2_session.get(settings.KEYCLOAK_USERINFO_URL).json() - return cls._process_token(token), cls._process_userinfo(user_info) + verify=self.keycloak_settings.VERIFY_SSL) + user_info = oauth2_session.get(self.keycloak_settings.KEYCLOAK_USERINFO_URL).json() + return self._process_token(token), self._process_userinfo(user_info) - @classmethod - def _get_token_and_user_info_redirect_flow(cls, client_credentials): + def _get_token_and_user_info_redirect_flow(self, client_credentials): oauth2_session = OAuth2Session(client_credentials.client_id, scope='openid', redirect_uri=client_credentials.redirect_uri, state=client_credentials.state) - token = oauth2_session.fetch_token(settings.KEYCLOAK_TOKEN_URL, + token = oauth2_session.fetch_token(self.keycloak_settings.KEYCLOAK_TOKEN_URL, client_secret=client_credentials.client_secret, authorization_response=client_credentials.authorization_code_url, - verify=settings.VERIFY_SSL) - user_info = oauth2_session.get(settings.KEYCLOAK_USERINFO_URL).json() - return cls._process_token(token), cls._process_userinfo(user_info) + verify=self.keycloak_settings.VERIFY_SSL) + user_info = oauth2_session.get(self.keycloak_settings.KEYCLOAK_USERINFO_URL).json() + return self._process_token(token), self._process_userinfo(user_info) - @classmethod - def _get_token_from_refresh_token(cls, client_credentials, refresh_token): + def _get_token_from_refresh_token(self, client_credentials, refresh_token): oauth2_session = OAuth2Session(client_credentials.client_id, scope='openid') auth = requests.auth.HTTPBasicAuth(client_credentials.client_id, client_credentials.client_secret) - token = oauth2_session.refresh_token(token_url=settings.KEYCLOAK_TOKEN_URL, + token = oauth2_session.refresh_token(token_url=self.keycloak_settings.KEYCLOAK_TOKEN_URL, refresh_token=refresh_token, auth=auth, - verify=settings.VERIFY_SSL) - return cls._process_token(token) + verify=self.keycloak_settings.VERIFY_SSL) + return self._process_token(token) + + def get_authorization_token(self, client_credentials, tenant_id, username=None): + """ + This method created a authorization token for the user or a service account + In case of a service account username will be null + :param client_credentials: object of class client_credentials + :param tenant_id: gateway id of the client + :param username: username of the user for which authorization token is being created + :return: AuthzToken + """ + client = BackendApplicationClient(client_id=client_credentials.client_id) + oauth = OAuth2Session(client=client) + token = oauth.fetch_token( + token_url=self.keycloak_settings.token_url, + client_id=client_credentials.client_id, + client_secret=client_credentials.client_secret, + verify=client_credentials.verify_ssl) + + access_token = token.get('access_token') + return AuthzToken( + accessToken=access_token, + claimsMap={'gatewayID': tenant_id, 'userName': username}) @classmethod def _process_token(cls, token): @@ -116,6 +146,16 @@ class KeycloakBackend(object): last_name = userinfo['family_name'] return UserInfo(username, email, first_name, last_name) + def _load_settings(self, configuration_file_location): + config = configparser.ConfigParser() + config.read(configuration_file_location) + settings = config['IAMSeverSettings'] + self.keycloak_settings.KEYCLOAK_AUTHORIZE_URL = settings['KEYCLOAK_AUTHORIZE_URL'] + self.keycloak_settings.KEYCLOAK_LOGOUT_URL = settings['KEYCLOAK_LOGOUT_URL'] + self.keycloak_settings.KEYCLOAK_TOKEN_URL = settings['KEYCLOAK_TOKEN_URL'] + self.keycloak_settings.KEYCLOAK_USERINFO_URL = settings['KEYCLOAK_USERINFO_URL'] + self.keycloak_settings.VERIFY_SSL = settings.getboolean('VERIFY_SSL') + class Token(object): diff --git a/clients/python/airavata_custos/settings.py b/clients/python/airavata_custos/settings.py index 89a05ef..dc4d101 100644 --- a/clients/python/airavata_custos/settings.py +++ b/clients/python/airavata_custos/settings.py @@ -15,19 +15,25 @@ # limitations under the License. # -KEYCLOAK_AUTHORIZE_URL = 'https://localhost:8443/auth/realms/default/protocol/openid-connect/auth' -KEYCLOAK_TOKEN_URL = 'https://localhost:8443/auth/realms/default/protocol/openid-connect/token' -KEYCLOAK_USERINFO_URL = 'https://localhost:8443/auth/realms/default/protocol/openid-connect/userinfo' -KEYCLOAK_LOGOUT_URL = 'https://localhost:8443/auth/realms/default/protocol/openid-connect/logout' + +class ProfileSettings(object): + # Profile Service Configuration + + def __init__(self): + self.PROFILE_SERVICE_HOST = '0.0.0.0' + self.PROFILE_SERVICE_PORT = '8081' + + +class IAMSettings(object): + + def __init__(self): + self.KEYCLOAK_AUTHORIZE_URL = 'https://localhost:8443/auth/realms/default/protocol/openid-connect/auth' + self.KEYCLOAK_TOKEN_URL = 'https://localhost:8443/auth/realms/default/protocol/openid-connect/token' + self.KEYCLOAK_USERINFO_URL = 'https://localhost:8443/auth/realms/default/protocol/openid-connect/userinfo' + self.KEYCLOAK_LOGOUT_URL = 'https://localhost:8443/auth/realms/default/protocol/openid-connect/logout' + self.VERIFY_SSL = False # Seconds each connection in the pool is able to stay alive. If open connection # has lived longer than this period, it will be closed. # (https://github.com/Thriftpy/thrift_connector) -THRIFT_CLIENT_POOL_KEEPALIVE = 5 - -# Profile Service Configuration -PROFILE_SERVICE_HOST = '0.0.0.0' -PROFILE_SERVICE_PORT = '8081' -PROFILE_SERVICE_SECURE = False - -VERIFY_SSL = False +THRIFT_CLIENT_POOL_KEEPALIVE = 5 \ No newline at end of file diff --git a/clients/python/airavata_custos/utils.py b/clients/python/airavata_custos/utils.py index 17fdf59..e025077 100644 --- a/clients/python/airavata_custos/utils.py +++ b/clients/python/airavata_custos/utils.py @@ -21,8 +21,8 @@ from thrift.protocol import TBinaryProtocol from thrift.protocol.TMultiplexedProtocol import TMultiplexedProtocol from thrift.transport import TSocket, TSSLSocket, TTransport -from airavata_custos import settings from custos.profile.iam.admin.services.cpi import IamAdminServices, constants +from airavata_custos import settings log = logging.getLogger(__name__) @@ -63,14 +63,16 @@ class CustomThriftClient(connection_pool.ThriftClient): class IAMAdminServiceThriftClient(MultiplexThriftClientMixin, CustomThriftClient): service_name = constants.IAM_ADMIN_SERVICES_CPI_NAME - secure = settings.PROFILE_SERVICE_SECURE + secure = True -iamadmin_client_pool = connection_pool.ClientPool( - IamAdminServices, - settings.PROFILE_SERVICE_HOST, - settings.PROFILE_SERVICE_PORT, - connection_class=IAMAdminServiceThriftClient, - keepalive=settings.THRIFT_CLIENT_POOL_KEEPALIVE -) +def initialize_iamadmin_client_pool(host, port): + iamadmin_client_pool = connection_pool.ClientPool( + IamAdminServices, + host, + port, + connection_class=IAMAdminServiceThriftClient, + keepalive=settings.THRIFT_CLIENT_POOL_KEEPALIVE + ) + return iamadmin_client_pool diff --git a/clients/python/requirements_dev.txt b/clients/python/requirements_dev.txt index 353a50e..e0209cb 100644 --- a/clients/python/requirements_dev.txt +++ b/clients/python/requirements_dev.txt @@ -15,4 +15,5 @@ oauthlib==3.1.0 requests_oauthlib==1.2.0 requests==2.22.0 thrift_connector==0.24 -thrift==0.11.0 \ No newline at end of file +thrift==0.11.0 +configparser==4.0.2