diff --git a/config_db_migrate/__init__.py b/config_db_migrate/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/config_db_migrate/versions/150800b30447_share_sessions_through_the_database.py b/config_db_migrate/versions/150800b30447_share_sessions_through_the_database.py new file mode 100644 index 0000000000..f363ece0af --- /dev/null +++ b/config_db_migrate/versions/150800b30447_share_sessions_through_the_database.py @@ -0,0 +1,30 @@ +"""Share sessions through the database + +Revision ID: 150800b30447 +Revises: 8268fc7ca7f4 +Create Date: 2017-11-23 15:26:45.594141 + +""" + +# revision identifiers, used by Alembic. +revision = '150800b30447' +down_revision = '8268fc7ca7f4' +branch_labels = None +depends_on = None + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.create_table('sessions', + sa.Column('auth_string', sa.CHAR(64), nullable=False), + sa.Column('token', sa.CHAR(32), nullable=False), + sa.Column('last_access', sa.DateTime(), nullable=False), + sa.PrimaryKeyConstraint('auth_string', + name=op.f('pk_sessions')) + ) + + +def downgrade(): + op.drop_table('sessions') diff --git a/config_db_migrate/versions/__init__.py b/config_db_migrate/versions/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libcodechecker/libclient/authentication_helper.py b/libcodechecker/libclient/authentication_helper.py index 2ea495117a..6be4f2be3b 100644 --- a/libcodechecker/libclient/authentication_helper.py +++ b/libcodechecker/libclient/authentication_helper.py @@ -5,9 +5,9 @@ # ------------------------------------------------------------------------- import os -import sys # import datetime import socket +import sys from thrift.transport import THttpClient from thrift.protocol import TJSONProtocol @@ -17,9 +17,10 @@ import shared from Authentication_v6 import codeCheckerAuthentication -from libcodechecker import session_manager from libcodechecker import util +from credential_manager import SESSION_COOKIE_NAME + class ThriftAuthHelper(): def __init__(self, protocol, host, port, uri, @@ -32,8 +33,7 @@ def __init__(self, protocol, host, port, uri, self.client = codeCheckerAuthentication.Client(self.protocol) if session_token: - headers = {'Cookie': session_manager.SESSION_COOKIE_NAME + - "=" + session_token} + headers = {'Cookie': SESSION_COOKIE_NAME + '=' + session_token} self.transport.setCustomHeaders(headers) # ------------------------------------------------------------ diff --git a/libcodechecker/libclient/client.py b/libcodechecker/libclient/client.py index fc70b469dd..1e559ee8e4 100644 --- a/libcodechecker/libclient/client.py +++ b/libcodechecker/libclient/client.py @@ -13,14 +13,14 @@ import shared from Authentication_v6 import ttypes as AuthTypes -from libcodechecker import session_manager from libcodechecker.logger import get_logger from libcodechecker.util import split_product_url from libcodechecker.version import CLIENT_API from . import authentication_helper -from . import thrift_helper from . import product_helper +from . import thrift_helper +from credential_manager import UserCredentials LOG = get_logger('system') @@ -46,8 +46,8 @@ def setup_auth_client(protocol, host, port, session_token=None): """ if not session_token: - manager = session_manager.SessionManager_Client() - session_token = manager.getToken(host, port) + manager = UserCredentials() + session_token = manager.get_token(host, port) session_token_new = perform_auth_for_handler(protocol, manager, host, port, @@ -79,8 +79,8 @@ def setup_auth_client_from_url(product_url, session_token=None): def handle_auth(protocol, host, port, username, login=False): - session = session_manager.SessionManager_Client() - auth_token = session.getToken(host, port) + session = UserCredentials() + auth_token = session.get_token(host, port) auth_client = authentication_helper.ThriftAuthHelper(protocol, host, port, '/v' + @@ -92,7 +92,7 @@ def handle_auth(protocol, host, port, username, login=False): if not login: logout_done = auth_client.destroySession() if logout_done: - session.saveToken(host, port, None, True) + session.save_token(host, port, None, True) LOG.info("Successfully logged out.") return @@ -116,7 +116,7 @@ def handle_auth(protocol, host, port, username, login=False): if 'Username:Password' in str(methods): # Try to use a previously saved credential from configuration file. - saved_auth = session.getAuthString(host, port) + saved_auth = session.get_auth_string(host, port) if saved_auth: LOG.info("Logging in using preconfigured credentials...") @@ -134,7 +134,7 @@ def handle_auth(protocol, host, port, username, login=False): username + ":" + pwd) - session.saveToken(host, port, session_token) + session.save_token(host, port, session_token) LOG.info("Server reported successful authentication.") except shared.ttypes.RequestFailed as reqfail: LOG.error("Authentication failed! Please check your credentials.") @@ -170,7 +170,7 @@ def perform_auth_for_handler(protocol, manager, host, port, print_err = False if manager.is_autologin_enabled(): - auto_auth_string = manager.getAuthString(host, port) + auto_auth_string = manager.get_auth_string(host, port) if auto_auth_string: # Try to automatically log in with a saved credential # if it exists for the server. @@ -178,7 +178,7 @@ def perform_auth_for_handler(protocol, manager, host, port, session_token = auth_client.performLogin( "Username:Password", auto_auth_string) - manager.saveToken(host, port, session_token) + manager.save_token(host, port, session_token) LOG.info("Authenticated using pre-configured " "credentials.") return session_token diff --git a/libcodechecker/libclient/credential_manager.py b/libcodechecker/libclient/credential_manager.py new file mode 100644 index 0000000000..b9b0912b12 --- /dev/null +++ b/libcodechecker/libclient/credential_manager.py @@ -0,0 +1,92 @@ +# ------------------------------------------------------------------------- +# The CodeChecker Infrastructure +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# ------------------------------------------------------------------------- +""" +Handles the management of stored user credentials and currently known session +tokens. +""" + +import json +import os +import stat + +import portalocker + +from libcodechecker.logger import get_logger +from libcodechecker.util import check_file_owner_rw +from libcodechecker.util import load_json_or_empty +from libcodechecker.version import SESSION_COOKIE_NAME as _SCN + +LOG = get_logger('system') +SESSION_COOKIE_NAME = _SCN + + +class UserCredentials: + + def __init__(self): + LOG.debug("Loading clientside session config.") + + # Check whether user's configuration exists. + user_home = os.path.expanduser("~") + session_cfg_file = os.path.join(user_home, + ".codechecker.passwords.json") + LOG.debug(session_cfg_file) + + scfg_dict = load_json_or_empty(session_cfg_file, {}, + "user authentication") + if os.path.exists(session_cfg_file): + check_file_owner_rw(session_cfg_file) + + if not scfg_dict.get('credentials'): + scfg_dict['credentials'] = {} + + self.__save = scfg_dict + self.__autologin = scfg_dict.get('client_autologin', True) + + # Check and load token storage for user. + self.token_file = os.path.join(user_home, ".codechecker.session.json") + LOG.debug(self.token_file) + + if os.path.exists(self.token_file): + token_dict = load_json_or_empty(self.token_file, {}, + "user authentication") + check_file_owner_rw(self.token_file) + + self.__tokens = token_dict.get('tokens') + else: + with open(self.token_file, 'w') as f: + json.dump({'tokens': {}}, f) + os.chmod(self.token_file, stat.S_IRUSR | stat.S_IWUSR) + + self.__tokens = {} + + def is_autologin_enabled(self): + return self.__autologin + + def get_token(self, host, port): + return self.__tokens.get("{0}:{1}".format(host, port)) + + def get_auth_string(self, host, port): + ret = self.__save['credentials'].get('{0}:{1}'.format(host, port)) + if not ret: + ret = self.__save['credentials'].get(host) + if not ret: + ret = self.__save['credentials'].get('*:{0}'.format(port)) + if not ret: + ret = self.__save['credentials'].get('*') + + return ret + + def save_token(self, host, port, token, destroy=False): + if destroy: + del self.__tokens['{0}:{1}'.format(host, port)] + else: + self.__tokens['{0}:{1}'.format(host, port)] = token + + with open(self.token_file, 'w') as scfg: + portalocker.lock(scfg, portalocker.LOCK_EX) + json.dump({'tokens': self.__tokens}, scfg, + indent=2, sort_keys=True) + portalocker.unlock(scfg) diff --git a/libcodechecker/libclient/product_helper.py b/libcodechecker/libclient/product_helper.py index ef284e22d3..f4025eab5a 100644 --- a/libcodechecker/libclient/product_helper.py +++ b/libcodechecker/libclient/product_helper.py @@ -16,9 +16,10 @@ import shared from ProductManagement_v6 import codeCheckerProductService -from libcodechecker import session_manager from libcodechecker import util +from credential_manager import SESSION_COOKIE_NAME + class ThriftProductHelper(object): def __init__(self, protocol, host, port, uri, session_token=None): @@ -30,8 +31,7 @@ def __init__(self, protocol, host, port, uri, session_token=None): self.client = codeCheckerProductService.Client(self.protocol) if session_token: - headers = {'Cookie': session_manager.SESSION_COOKIE_NAME + - "=" + session_token} + headers = {'Cookie': SESSION_COOKIE_NAME + '=' + session_token} self.transport.setCustomHeaders(headers) # ------------------------------------------------------------ diff --git a/libcodechecker/libclient/thrift_helper.py b/libcodechecker/libclient/thrift_helper.py index 59fdd0c4bc..ce7583b878 100644 --- a/libcodechecker/libclient/thrift_helper.py +++ b/libcodechecker/libclient/thrift_helper.py @@ -16,10 +16,10 @@ import shared from codeCheckerDBAccess_v6 import codeCheckerDBAccess -from libcodechecker import session_manager from libcodechecker import util from libcodechecker.logger import get_logger +from credential_manager import SESSION_COOKIE_NAME LOG = get_logger('system') @@ -35,8 +35,7 @@ def __init__(self, protocol, host, port, uri, session_token=None): self.client = codeCheckerDBAccess.Client(self.protocol) if session_token: - headers = {'Cookie': session_manager.SESSION_COOKIE_NAME + - "=" + session_token} + headers = {'Cookie': SESSION_COOKIE_NAME + '=' + session_token} self.transport.setCustomHeaders(headers) def ThriftClientCall(function): diff --git a/libcodechecker/libhandlers/server.py b/libcodechecker/libhandlers/server.py index a6233748dd..c520fb6433 100644 --- a/libcodechecker/libhandlers/server.py +++ b/libcodechecker/libhandlers/server.py @@ -26,11 +26,10 @@ from libcodechecker import host_check from libcodechecker import logger from libcodechecker import output_formatters -from libcodechecker import session_manager from libcodechecker import util from libcodechecker.analyze import analyzer_env -from libcodechecker.server import server from libcodechecker.server import instance_manager +from libcodechecker.server import server from libcodechecker.server.database import database from libcodechecker.server.database import database_status from libcodechecker.server.database.config_db_model \ @@ -679,8 +678,6 @@ def server_init_start(args): context = generic_package_context.get_context() context.codechecker_workspace = args.config_directory - session_manager.SessionManager.CodeChecker_Workspace = \ - args.config_directory context.db_username = args.dbusername check_env = analyzer_env.get_check_env(context.path_env_extra, @@ -861,7 +858,7 @@ def server_init_start(args): def main(args): """ Setup a logger server based on the configuration and - manage the Codechecker server. + manage the CodeChecker server. """ with logger.LOG_CFG_SERVER(args.verbose): server_init_start(args) diff --git a/libcodechecker/server/api/authentication.py b/libcodechecker/server/api/authentication.py index 9c8985c669..a24623f9b8 100644 --- a/libcodechecker/server/api/authentication.py +++ b/libcodechecker/server/api/authentication.py @@ -48,7 +48,7 @@ def getAuthParameters(self): token = None if self.__auth_session: token = self.__auth_session.token - return HandshakeInformation(self.__manager.isEnabled(), + return HandshakeInformation(self.__manager.is_enabled, self.__manager.is_valid( token, True)) diff --git a/libcodechecker/server/database/config_db_model.py b/libcodechecker/server/database/config_db_model.py index e3883eefd5..6855e37e0e 100644 --- a/libcodechecker/server/database/config_db_model.py +++ b/libcodechecker/server/database/config_db_model.py @@ -9,6 +9,7 @@ from __future__ import print_function from __future__ import unicode_literals +from datetime import datetime import sys from sqlalchemy import * @@ -107,6 +108,21 @@ def __init__(self, permission, product_id, name, is_group=False): self.is_group = is_group +class Session(Base): + __tablename__ = 'sessions' + + # Auth-String is a SHA-256 hash, while token is a Python UUID which is + # 32 characters (both in hex expanded format). + auth_string = Column(CHAR(64), nullable=False, primary_key=True) + token = Column(CHAR(32), nullable=False) + last_access = Column(DateTime, nullable=False) + + def __init__(self, auth_string, token): + self.auth_string = auth_string + self.token = token + self.last_access = datetime.now() + + IDENTIFIER = { 'identifier': "ConfigDatabase", 'orm_meta': CC_META, diff --git a/libcodechecker/server/server.py b/libcodechecker/server/server.py index f8865c826b..ff84da7837 100644 --- a/libcodechecker/server/server.py +++ b/libcodechecker/server/server.py @@ -16,10 +16,11 @@ import os import posixpath from random import sample -import stat +import shutil import socket import ssl import sys +import stat import urllib try: @@ -40,13 +41,13 @@ from codeCheckerDBAccess_v6 import codeCheckerDBAccess as ReportAPI_v6 from ProductManagement_v6 import codeCheckerProductService as ProductAPI_v6 -from libcodechecker import session_manager from libcodechecker.logger import get_logger from libcodechecker.util import get_tmp_dir_hash from . import instance_manager from . import permissions from . import routing +from . import session_manager from api.authentication import ThriftAuthHandler as AuthHandler_v6 from api.bad_api_version import ThriftAPIMismatchHandler as BadAPIHandler from api.product_server import ThriftProductHandler as ProductHandler_v6 @@ -84,7 +85,7 @@ def __check_auth_in_request(self): present. """ - if not self.server.manager.isEnabled(): + if not self.server.manager.is_enabled: return None success = None @@ -153,18 +154,18 @@ def do_GET(self): LOG.info("{0}:{1} -- [{2}] GET {3}" .format(self.client_address[0], str(self.client_address[1]), - auth_session.user if auth_session else "Anonymous", + auth_session.user if auth_session else 'Anonymous', self.path)) - if self.server.manager.isEnabled() and not auth_session: - realm = self.server.manager.getRealm()["realm"] - error_body = self.server.manager.getRealm()["error"] + if self.server.manager.is_enabled and not auth_session: + realm = self.server.manager.get_realm()['realm'] + error_body = self.server.manager.get_realm()['error'] self.send_response(401) # 401 Unauthorised - self.send_header("WWW-Authenticate", + self.send_header('WWW-Authenticate', 'Basic realm="{0}"'.format(realm)) - self.send_header("Content-type", "text/plain") - self.send_header("Content-length", str(len(error_body))) + self.send_header('Content-type', 'text/plain') + self.send_header('Content-length', str(len(error_body))) self.send_header('Connection', 'close') self.end_headers() self.wfile.write(error_body) @@ -344,7 +345,7 @@ def do_POST(self): iprot = input_protocol_factory.getProtocol(itrans) oprot = output_protocol_factory.getProtocol(otrans) - if self.server.manager.isEnabled() and \ + if self.server.manager.is_enabled and \ not self.path.endswith('/Authentication') and \ not auth_session: # Bail out if the user is not authenticated... @@ -687,6 +688,7 @@ def __init__(self, LOG.debug("Creating database engine for CONFIG DATABASE...") self.__engine = product_db_sql_server.create_engine() self.config_session = sessionmaker(bind=self.__engine) + self.manager.set_database_connection(self.config_session) # Load the initial list of products and set up the server. cfg_sess = self.config_session() @@ -855,7 +857,7 @@ def __make_root_file(root_file): return sha -def start_server(config_directory, package_data, port, db_conn_string, +def start_server(config_directory, package_data, port, config_sql_server, suppress_handler, listen_address, force_auth, skip_db_cleanup, context, check_env): """ @@ -884,16 +886,41 @@ def start_server(config_directory, package_data, port, db_conn_string, .format(root_file)) root_sha = __make_root_file(root_file) + # Check whether configuration file exists, create an example if not. + server_cfg_file = os.path.join(config_directory, 'server_config.json') + if not os.path.exists(server_cfg_file): + # For backward compatibility reason if the session_config.json file + # exists we rename it to server_config.json. + session_cfg_file = os.path.join(config_directory, + 'session_config.json') + example_cfg_file = os.path.join(os.environ['CC_PACKAGE_ROOT'], + 'config', 'server_config.json') + if os.path.exists(session_cfg_file): + LOG.info("Renaming '{0}' to '{1}'. Please check the example " + "configuration file ('{2}') or the user guide for more " + "information.".format(session_cfg_file, + server_cfg_file, + example_cfg_file)) + os.rename(session_cfg_file, server_cfg_file) + else: + LOG.info("CodeChecker server's example configuration file " + "created at '{0}'".format(server_cfg_file)) + shutil.copyfile(example_cfg_file, server_cfg_file) + try: - manager = session_manager.SessionManager(root_sha, force_auth) + manager = session_manager.SessionManager( + server_cfg_file, + config_sql_server.get_connection_string(), + root_sha, + force_auth) except IOError, ValueError: - LOG.error("The server's authentication config file is invalid!") + LOG.error("The server's configuration file is invalid!") sys.exit(1) http_server = CCSimpleHttpServer(server_addr, RequestHandler, config_directory, - db_conn_string, + config_sql_server, skip_db_cleanup, package_data, suppress_handler, diff --git a/libcodechecker/server/session_manager.py b/libcodechecker/server/session_manager.py new file mode 100644 index 0000000000..457ec8e4c5 --- /dev/null +++ b/libcodechecker/server/session_manager.py @@ -0,0 +1,539 @@ +# ------------------------------------------------------------------------- +# The CodeChecker Infrastructure +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# ------------------------------------------------------------------------- +""" +Handles the management of authentication sessions on the server's side. +""" + +from datetime import datetime +import hashlib +import os +import uuid + +from sqlalchemy import and_ + +from libcodechecker.logger import get_logger +from libcodechecker.util import check_file_owner_rw +from libcodechecker.util import load_json_or_empty +from libcodechecker.version import SESSION_COOKIE_NAME as _SCN + +from database.config_db_model import Session as SessionRecord + +UNSUPPORTED_METHODS = [] + +try: + from libcodechecker.libauth import cc_ldap +except ImportError: + UNSUPPORTED_METHODS.append('ldap') + +try: + from libcodechecker.libauth import cc_pam +except ImportError: + UNSUPPORTED_METHODS.append('pam') + + +LOG = get_logger("server") +SESSION_COOKIE_NAME = _SCN + + +class _Session(object): + """A session for an authenticated, privileged client connection.""" + + @staticmethod + def calc_persistency_hash(auth_string, salt=None): + """Calculates a more secure persistency hash for the session. This + persistency hash is intended to be used for the "session recycle" + feature to prevent NAT endpoints from accidentally getting each + other's session.""" + return hashlib.sha256(auth_string + '@' + + salt if salt else 'CodeChecker').hexdigest() + + def __init__(self, token, phash, username, groups, + is_root=False, database=None): + self.last_access = datetime.now() + self.token = token + self.persistent_hash = phash + self.user = username + self.groups = groups + self.__root = is_root + self.__database = database + + @property + def is_root(self): + """Returns whether or not the Session was created with the master + superuser (root) credentials.""" + return self.__root + + def still_valid(self, soft_lifetime, hard_lifetime, do_revalidate=False): + """ + Returns if the session is still valid, and optionally revalidates + it. A session is valid in its soft-lifetime. + """ + + if (datetime.now() - self.last_access).total_seconds() <= \ + soft_lifetime and self.still_reusable(hard_lifetime): + # If the session is still valid within the "reuse enabled" (soft) + # past and the check comes from a real user access, we revalidate + # the session by extending its lifetime --- the user retains their + # data. + if do_revalidate: + self.revalidate(soft_lifetime, hard_lifetime) + + # The session is still valid if it has been used in the past + # (length of "past" is up to server host). + return True + + # If the session is older than the "soft" limit, + # the user needs to authenticate again. + return False + + def still_reusable(self, hard_lifetime): + """Returns whether the session is still reusable, ie. within its + hard lifetime: while a session is reusable, a valid authentication + from the session's user will return the user to the session.""" + return (datetime.now() - self.last_access).total_seconds() <= \ + hard_lifetime + + def revalidate(self, soft_lifetime, hard_lifetime): + if self.still_reusable(hard_lifetime): + # A session is only revalidated if it has yet to exceed its + # "hard" lifetime. After a session hasn't been used for this + # timeframe, it can NOT be resurrected at all --- the user needs + # to log in into a brand-new session. + self.last_access = datetime.now() + + if self.__database and not self.still_reusable(soft_lifetime): + # Update the timestamp in the database for the session's last + # access. We only do this if the soft-lifetime has expired so + # that not EVERY API requests' EVERY session check creates a + # database write. + try: + transaction = self.__database() + record = transaction.query(SessionRecord). \ + get(self.persistent_hash) + + if record: + record.last_access = self.last_access + transaction.commit() + except Exception as e: + LOG.error("Couldn't update usage timestamp of {0}" + .format(self.token)) + LOG.error(str(e)) + finally: + transaction.close() + + +class SessionManager: + """ + Provides the functionality required to handle user authentication on a + CodeChecker server. + """ + + def __init__(self, configuration_file, session_salt, + root_sha, force_auth=False): + """ + Initialise a new Session Manager on the server. + + :param configuration_file: The configuration file to read + authentication backends from. + :param session_salt: An initial salt that will be used in hashing + the session to the database. + :param root_sha: The SHA-256 hash of the root user's authentication. + :param force_auth: If True, the manager will be enabled even if the + configuration file disables authentication. + """ + self.__database_connection = None + self.__logins_since_prune = 0 + self.__sessions = [] + self.__session_salt = hashlib.sha1(session_salt).hexdigest() + + LOG.debug(configuration_file) + scfg_dict = load_json_or_empty(configuration_file, {}, + 'server configuration') + if scfg_dict != {}: + check_file_owner_rw(configuration_file) + else: + # If the configuration dict is empty, it means a JSON couldn't + # have been parsed from it. + raise ValueError("Server configuration file was invalid, or " + "empty.") + + self.__max_run_count = scfg_dict['max_run_count'] \ + if 'max_run_count' in scfg_dict else None + + self.__auth_config = scfg_dict['authentication'] + + if force_auth: + LOG.debug("Authentication was force-enabled.") + self.__auth_config['enabled'] = True + + # Save the root SHA into the configuration (but only in memory!) + self.__auth_config['method_root'] = root_sha + + # If no methods are configured as enabled, disable authentication. + if scfg_dict['authentication'].get('enabled'): + found_auth_method = False + + if 'method_dictionary' in self.__auth_config and \ + self.__auth_config['method_dictionary'].get('enabled'): + found_auth_method = True + + if 'method_ldap' in self.__auth_config and \ + self.__auth_config['method_ldap'].get('enabled'): + if 'ldap' not in UNSUPPORTED_METHODS: + found_auth_method = True + else: + LOG.warning("LDAP authentication was enabled but " + "prerequisites are NOT installed on the system" + "... Disabling LDAP authentication.") + self.__auth_config['method_ldap']['enabled'] = False + + if 'method_pam' in self.__auth_config and \ + self.__auth_config['method_pam'].get('enabled'): + if 'pam' not in UNSUPPORTED_METHODS: + found_auth_method = True + else: + LOG.warning("PAM authentication was enabled but " + "prerequisites are NOT installed on the system" + "... Disabling PAM authentication.") + self.__auth_config['method_pam']['enabled'] = False + + if not found_auth_method: + if force_auth: + LOG.warning("Authentication was manually enabled, but no " + "valid authentication backends are " + "configured... The server will only allow " + "the master superuser (root) access.") + else: + LOG.warning("Authentication is enabled but no valid " + "authentication backends are configured... " + "Falling back to no authentication.") + self.__auth_config['enabled'] = False + + @property + def is_enabled(self): + return self.__auth_config.get('enabled') + + def get_realm(self): + return { + "realm": self.__auth_config.get('realm_name'), + "error": self.__auth_config.get('realm_error') + } + + def set_database_connection(self, connection): + """ + Set the instance's database connection to use in fetching + database-stored sessions to the given connection. + + Use None as connection's value to unset the database. + """ + self.__database_connection = connection + + def __handle_validation(self, auth_string): + """ + Validate an oncoming authorization request + against some authority controller. + + Returns False if no validation was done, or a validation object + if the user was successfully authenticated. + + This validation object contains two keys: username and groups. + """ + validation = self.__try_auth_dictionary(auth_string) + if validation: + return validation + + validation = self.__try_auth_pam(auth_string) + if validation: + return validation + + validation = self.__try_auth_ldap(auth_string) + if validation: + return validation + + return False + + def __is_method_enabled(self, method): + return method not in UNSUPPORTED_METHODS and \ + 'method_' + method in self.__auth_config and \ + self.__auth_config['method_' + method].get('enabled') + + def __try_auth_root(self, auth_string): + """ + Try to authenticate the user against the root username:password's hash. + """ + if 'method_root' in self.__auth_config and \ + hashlib.sha256(auth_string).hexdigest() == \ + self.__auth_config['method_root']: + return { + 'username': SessionManager.get_user_name(auth_string), + 'groups': [], + 'root': True + } + + return False + + def __try_auth_dictionary(self, auth_string): + """ + Try to authenticate the user against the hardcoded credential list. + + Returns a validation object if successful, which contains the users' + groups. + """ + method_config = self.__auth_config.get('method_dictionary') + if not method_config: + return False + + valid = self.__is_method_enabled('dictionary') and \ + auth_string in method_config.get('auths') + if not valid: + return False + + username = SessionManager.get_user_name(auth_string) + group_list = method_config['groups'][username] if \ + 'groups' in method_config and \ + username in method_config['groups'] else [] + + return { + 'username': username, + 'groups': group_list + } + + def __try_auth_pam(self, auth_string): + """ + Try to authenticate user based on the PAM configuration. + """ + if self.__is_method_enabled('pam'): + username, password = auth_string.split(':') + if cc_pam.auth_user(self.__auth_config['method_pam'], + username, password): + # PAM does not hold a group membership list we can reliably + # query. + return {'username': username} + + return False + + def __try_auth_ldap(self, auth_string): + """ + Try to authenticate user to all the configured authorities. + """ + if self.__is_method_enabled('ldap'): + username, password = auth_string.split(':') + + ldap_authorities = self.__auth_config['method_ldap'] \ + .get('authorities') + for ldap_conf in ldap_authorities: + if cc_ldap.auth_user(ldap_conf, username, password): + groups = cc_ldap.get_groups(ldap_conf, username, password) + return {'username': username, 'groups': groups} + + return False + + @staticmethod + def get_user_name(auth_string): + return auth_string.split(':')[0] + + def _fetch_session_or_token(self, persistency_hash): + """ + Contact the session store to try fetching a valid session or token + for the given session object hash. This first uses the instance's + in-memory storage, and if nothing is found, contacts the database + (if connected). + + Returns a _Session object if found locally. + Returns a string token if it was found in the database. + None if a session wasn't found. + """ + + # Try the local store first. + sessions = (s for s in self.__sessions + if self.__still_reusable(s) and + s.persistent_hash == persistency_hash) + session = next(sessions, None) + + if not session and self.__database_connection: + try: + # Try the database, if it is connected. + transaction = self.__database_connection() + db_record = transaction.query(SessionRecord) \ + .get(persistency_hash) + + if db_record: + if (datetime.now() - db_record.last_access). \ + total_seconds() <= \ + self.__auth_config['session_lifetime']: + # If a token was found in the database and the session + # for it can still be resurrected, we reuse this token. + return db_record.token + else: + # The token has expired, remove it from the database. + transaction.delete(db_record) + transaction.commit() + except Exception as e: + LOG.error("Couldn't check login in the database: ") + LOG.error(str(e)) + finally: + if transaction: + transaction.close() + + return session + + def create_or_get_session(self, auth_string): + """Create a new session for the given auth-string, if it is valid. If + an existing session is found, return that instead. + Currently only username:password format auth_string + is supported. + """ + if not self.__auth_config['enabled']: + return None + + self.__logins_since_prune += 1 + if self.__logins_since_prune >= \ + self.__auth_config['logins_until_cleanup']: + self.__cleanup_sessions() + + validation = self.__try_auth_root(auth_string) + if not validation: + validation = self.__handle_validation(auth_string) + + if validation: + sess_hash = _Session.calc_persistency_hash(self.__session_salt, + auth_string) + local_session, db_token = None, None + + # If the session is still valid and credentials are resent, + # return old token. This is fetched either locally or from the db. + session_or_token = self._fetch_session_or_token(sess_hash) + if session_or_token: + if isinstance(session_or_token, _Session): + # We were able to fetch a session from the local in-memory + # storage. + local_session = session_or_token + self.__still_valid(local_session, do_revalidate=True) + elif isinstance(session_or_token, basestring): + # The database gave us a token, which we will reuse in + # creating a local cache entry for the session. + db_token = session_or_token + + if not local_session: + # If there isn't a Session locally, create it. + token = db_token if db_token else \ + uuid.UUID(bytes=os.urandom(16)).__str__().replace('-', '') + + user_name = validation['username'] + groups = validation.get('groups', []) + is_root = validation.get('root', False) + + local_session = _Session(token, sess_hash, + user_name, groups, is_root, + self.__database_connection) + self.__sessions.append(local_session) + + if self.__database_connection: + if not db_token: + # If db_token is None, the session was created + # brand new. + try: + transaction = self.__database_connection() + record = SessionRecord(sess_hash, token) + transaction.add(record) + transaction.commit() + except Exception as e: + LOG.error("Couldn't store login into database: ") + LOG.error(str(e)) + finally: + if transaction: + transaction.close() + else: + # The local session was created from a token + # already present in the database, thus we can't + # add a new one. + self.__still_valid(local_session, do_revalidate=True) + + return local_session + + def is_valid(self, token, access=False): + """Validates a given token (cookie) against + the known list of privileged sessions.""" + if not self.is_enabled: + return True + + return any(sess.token == token and self.__still_valid(sess, access) + for sess in self.__sessions) + + def get_max_run_count(self): + """ + Returns the maximum storable run count. If the value is None it means + we can upload unlimited number of runs. + """ + return self.__max_run_count + + def get_session(self, token, access=False): + """Gets the privileged session object based + based on the token. + """ + if not self.is_enabled: + return None + + for sess in self.__sessions: + if sess.token == token and self.__still_valid(sess, access): + return sess + + def invalidate(self, token): + """ + Remove a user's previous session from the store. + """ + + try: + transaction = self.__database_connection() \ + if self.__database_connection else None + + for session in self.__sessions[:]: + if session.token == token: + self.__sessions.remove(session) + + if transaction: + transaction.query(SessionRecord). \ + filter(and_(SessionRecord.auth_string == + session.persistent_hash, + SessionRecord.token == token)). \ + delete() + transaction.commit() + + return True + except Exception as e: + LOG.error("Couldn't invalidate session for token {0}" + .format(token)) + LOG.error(str(e)) + finally: + if transaction: + transaction.close() + + return False + + def __cleanup_sessions(self): + tokens = [s.token for s in self.__sessions + if not self.__still_reusable(s)] + self.__logins_since_prune = 0 + + for token in tokens: + self.invalidate(token) + + def __still_reusable(self, session): + """ + Helper function for checking if the session could be + resurrected, even if the soft grace period has expired. + """ + return session.still_reusable(self.__auth_config['session_lifetime']) + + def __still_valid(self, session, do_revalidate=False): + """ + Helper function for checking the validity of a session and + optionally resurrecting it (if possible). This function uses the + current instance's grace periods. + """ + return session.still_valid(self.__auth_config['soft_expire'], + self.__auth_config['session_lifetime'], + do_revalidate) diff --git a/libcodechecker/session_manager.py b/libcodechecker/session_manager.py deleted file mode 100644 index 0ba614bab9..0000000000 --- a/libcodechecker/session_manager.py +++ /dev/null @@ -1,511 +0,0 @@ -# ------------------------------------------------------------------------- -# The CodeChecker Infrastructure -# This file is distributed under the University of Illinois Open Source -# License. See LICENSE.TXT for details. -# ------------------------------------------------------------------------- -""" -Handles the allocation and destruction of privileged sessions associated -with a particular CodeChecker server. -""" - -from datetime import datetime -import hashlib -import json -import os -import shutil -import stat -import time -import uuid - -import portalocker - -from libcodechecker.logger import get_logger -from libcodechecker import util - -unsupported_methods = [] - -try: - from libcodechecker.libauth import cc_ldap -except ImportError: - unsupported_methods.append("ldap") - -try: - from libcodechecker.libauth import cc_pam -except ImportError: - unsupported_methods.append("pam") - -LOG = get_logger("server") -SESSION_COOKIE_NAME = "__ccPrivilegedAccessToken" -session_lifetimes = {} - - -class _Session(object): - """A session for an authenticated, privileged client connection.""" - - # Create an initial salt from system environment for use with the session - # permanent persistency routine. - __initial_salt = hashlib.sha256(SESSION_COOKIE_NAME + "__" + - str(time.time()) + "__" + - os.urandom(16)).hexdigest() - - @staticmethod - def calc_persistency_hash(auth_string): - """Calculates a more secure persistency hash for the session. This - persistency hash is intended to be used for the "session recycle" - feature to prevent NAT endpoints from accidentally getting each - other's session.""" - return hashlib.sha256(auth_string + "@" + - _Session.__initial_salt).hexdigest() - - def __init__(self, token, phash, username, groups, is_root=False): - self.last_access = datetime.now() - self.token = token - self.persistent_hash = phash - self.user = username - self.groups = groups - - self.__root = is_root - - @property - def is_root(self): - """Returns whether or not the Session was created with the master - superuser (root) credentials.""" - return self.__root - - def still_valid(self, do_revalidate=False): - """Returns if the session is still valid, and optionally revalidates - it. A session is valid in its soft-lifetime.""" - - if (datetime.now() - self.last_access).total_seconds() <= \ - session_lifetimes["soft"] and self.still_reusable(): - # If the session is still valid within the "reuse enabled" (soft) - # past and the check comes from a real user access, we revalidate - # the session by extending its lifetime --- the user retains their - # data. - if do_revalidate: - self.revalidate() - - # The session is still valid if it has been used in the past - # (length of "past" is up to server host). - return True - - # If the session is older than the "soft" limit, - # the user needs to authenticate again. - return False - - def still_reusable(self): - """Returns whether the session is still reusable, ie. within its - hard lifetime: while a session is reusable, a valid authentication - from the session's user will return the user to the session.""" - return (datetime.now() - self.last_access).total_seconds() <= \ - session_lifetimes["hard"] - - def revalidate(self): - if self.still_reusable(): - # A session is only revalidated if it has yet to exceed its - # "hard" lifetime. After a session hasn't been used for this - # timeframe, it can NOT be resurrected at all --- the user needs - # to log in into a brand-new session. - self.last_access = datetime.now() - - -def check_file_owner_rw(file_to_check): - """ - Check the file permissions. - Return: - True if only the owner can read or write the file. - False if other users or groups can read or write the file. - """ - - mode = os.stat(file_to_check)[stat.ST_MODE] - if mode & stat.S_IRGRP \ - or mode & stat.S_IWGRP \ - or mode & stat.S_IROTH \ - or mode & stat.S_IWOTH: - LOG.warning("'{0}' is readable by users other than you!" - " This poses a risk of leaking sensitive" - " information, such as passwords, session tokens, etc.!\n" - "Please 'chmod 0600 {0}' so only you can access the file." - .format(file_to_check)) - return False - return True - - -def load_server_cfg(server_cfg_file): - """ - Tries to load the session config file which should be a - valid json file, if loading fails returns an empty dict. - """ - - if os.path.exists(server_cfg_file): - check_file_owner_rw(server_cfg_file) - return util.load_json_or_empty(server_cfg_file, {}) - - return {} - - -class SessionManager: - CodeChecker_Workspace = None - - __valid_sessions = [] - __logins_since_prune = 0 - - def __init__(self, root_sha, force_auth=False): - LOG.debug('Loading session config') - - # Check whether workspace's configuration exists. - server_cfg_file = os.path.join(SessionManager.CodeChecker_Workspace, - "server_config.json") - - if not os.path.exists(server_cfg_file): - # For backward compatibility reason if the session_config.json file - # exists we rename it to server_config.json. - session_cfg_file = os.path.join( - SessionManager.CodeChecker_Workspace, - "session_config.json") - example_cfg_file = os.path.join(os.environ['CC_PACKAGE_ROOT'], - "config", "server_config.json") - if os.path.exists(session_cfg_file): - LOG.info("Renaming {0} to {1}. Please check the example " - "configuration file ({2}) or the user guide for " - "more information.".format(session_cfg_file, - server_cfg_file, - example_cfg_file)) - os.rename(session_cfg_file, server_cfg_file) - else: - LOG.info("CodeChecker server's example configuration file " - "created at " + server_cfg_file) - shutil.copyfile(example_cfg_file, server_cfg_file) - - LOG.debug(server_cfg_file) - - # Create the default settings and then load the file from the disk. - scfg_dict = {'authentication': {'enabled': False}} - scfg_dict.update(load_server_cfg(server_cfg_file)) - - self.__max_run_count = scfg_dict["max_run_count"] \ - if "max_run_count" in scfg_dict else None - - self.__auth_config = scfg_dict["authentication"] - - if force_auth: - LOG.debug("Authentication was force-enabled.") - self.__auth_config['enabled'] = True - - # Save the root SHA into the configuration (but only in memory!) - self.__auth_config['method_root'] = root_sha - - # If no methods are configured as enabled, disable authentication. - if scfg_dict["authentication"].get("enabled"): - found_auth_method = False - - if "method_dictionary" in self.__auth_config and \ - self.__auth_config["method_dictionary"].get("enabled"): - found_auth_method = True - - if "method_ldap" in self.__auth_config and \ - self.__auth_config["method_ldap"].get("enabled"): - if "ldap" not in unsupported_methods: - found_auth_method = True - else: - LOG.warning("LDAP authentication was enabled but " - "prerequisites are NOT installed on the system" - "... Disabling LDAP authentication.") - self.__auth_config["method_ldap"]["enabled"] = False - - if "method_pam" in self.__auth_config and \ - self.__auth_config["method_pam"].get("enabled"): - if "pam" not in unsupported_methods: - found_auth_method = True - else: - LOG.warning("PAM authentication was enabled but " - "prerequisites are NOT installed on the system" - "... Disabling PAM authentication.") - self.__auth_config["method_pam"]["enabled"] = False - - # - if not found_auth_method: - if force_auth: - LOG.warning("Authentication was manually enabled, but no " - "valid authentication backends are " - "configured... The server will only allow " - "the master superuser (root) access.") - else: - LOG.warning("Authentication is enabled but no valid " - "authentication backends are configured... " - "Falling back to no authentication.") - self.__auth_config["enabled"] = False - - session_lifetimes["soft"] = \ - self.__auth_config.get("soft_expire") or 60 - session_lifetimes["hard"] = \ - self.__auth_config.get("session_lifetime") or 300 - - def isEnabled(self): - return self.__auth_config.get("enabled") - - def getRealm(self): - return { - "realm": self.__auth_config.get("realm_name"), - "error": self.__auth_config.get("realm_error") - } - - def __handle_validation(self, auth_string): - """ - Validate an oncoming authorization request - against some authority controller. - - Returns False if no validation was done, or a validation object - if the user was successfully authenticated. - - This validation object contains two keys: username and groups. - """ - validation = self.__try_auth_dictionary(auth_string) - if validation: - return validation - - validation = self.__try_auth_pam(auth_string) - if validation: - return validation - - validation = self.__try_auth_ldap(auth_string) - if validation: - return validation - - return False - - def __is_method_enabled(self, method): - return method not in unsupported_methods and \ - "method_" + method in self.__auth_config and \ - self.__auth_config["method_" + method].get("enabled") - - def __try_auth_root(self, auth_string): - """ - Try to authenticate the user against the root username:password's hash. - """ - if "method_root" in self.__auth_config and \ - hashlib.sha256(auth_string).hexdigest() == \ - self.__auth_config["method_root"]: - return { - 'username': SessionManager.get_user_name(auth_string), - 'groups': [], - 'root': True - } - - return False - - def __try_auth_dictionary(self, auth_string): - """ - Try to authenticate the user against the hardcoded credential list. - - Returns a validation object if successful, which contains the users' - groups. - """ - method_config = self.__auth_config.get("method_dictionary") - if not method_config: - return False - - valid = self.__is_method_enabled("dictionary") and \ - auth_string in method_config.get("auths") - if not valid: - return False - - username = SessionManager.get_user_name(auth_string) - group_list = method_config['groups'][username] if \ - 'groups' in method_config and \ - username in method_config['groups'] else [] - - return { - 'username': username, - 'groups': group_list - } - - def __try_auth_pam(self, auth_string): - """ - Try to authenticate user based on the PAM configuration. - """ - if self.__is_method_enabled("pam"): - username, password = auth_string.split(":") - if cc_pam.auth_user(self.__auth_config["method_pam"], - username, password): - # PAM does not hold a group membership list we can reliably - # query. - return {'username': username} - - return False - - def __try_auth_ldap(self, auth_string): - """ - Try to authenticate user to all the configured authorities. - """ - if self.__is_method_enabled("ldap"): - username, password = auth_string.split(":") - - ldap_authorities = self.__auth_config["method_ldap"] \ - .get("authorities") - for ldap_conf in ldap_authorities: - if cc_ldap.auth_user(ldap_conf, username, password): - groups = cc_ldap.get_groups(ldap_conf, username, password) - return {'username': username, 'groups': groups} - - return False - - @staticmethod - def get_user_name(auth_string): - return auth_string.split(":")[0] - - def create_or_get_session(self, auth_string): - """Create a new session for the given auth-string, if it is valid. If - an existing session is found, return that instead. - Currently only username:password format auth_string - is supported. - """ - if not self.__auth_config["enabled"]: - return None - - self.__logins_since_prune += 1 - if self.__logins_since_prune >= \ - self.__auth_config["logins_until_cleanup"]: - self.__cleanup_sessions() - - validation = self.__try_auth_root(auth_string) - if not validation: - validation = self.__handle_validation(auth_string) - - if validation: - # If the session is still valid and credentials - # are resent return old token. - session_already = next( - (s for s - in SessionManager.__valid_sessions if s.still_reusable() and - s.persistent_hash == - _Session.calc_persistency_hash(auth_string)), - None) - - if session_already: - session_already.revalidate() - session = session_already - else: - # TODO: Use a more secure way for token generation? - token = uuid.UUID(bytes=os.urandom(16)).__str__().replace("-", - "") - - user_name = validation['username'] - groups = validation.get("groups", []) - is_root = validation.get('root', False) - - session = _Session(token, - _Session.calc_persistency_hash(auth_string), - user_name, groups, is_root) - SessionManager.__valid_sessions.append(session) - - return session - - return None - - def is_valid(self, token, access=False): - """Validates a given token (cookie) against - the known list of privileged sessions.""" - if not self.isEnabled(): - return True - else: - return any(_sess.token == token and _sess.still_valid(access) - for _sess in SessionManager.__valid_sessions) - - def get_max_run_count(self): - """ - Returns the maximum storable run count. If the value is None it means - we can upload unlimited number of runs. - """ - return self.__max_run_count - - def get_session(self, token, access=False): - """Gets the privileged session object based - based on the token. - """ - if not self.isEnabled(): - return None - for _sess in SessionManager.__valid_sessions: - if _sess.token == token and _sess.still_valid(access): - return _sess - return None - - def invalidate(self, token): - """Remove a user's previous session from the store.""" - for session in SessionManager.__valid_sessions[:]: - if session.token == token: - SessionManager.__valid_sessions.remove(session) - return True - - return False - - def __cleanup_sessions(self): - SessionManager.__valid_sessions = [s for s - in SessionManager.__valid_sessions - if s.still_reusable()] - self.__logins_since_prune = 0 - - -class SessionManager_Client: - def __init__(self): - LOG.debug('Loading session config') - - # Check whether user's configuration exists. - user_home = os.path.expanduser("~") - session_cfg_file = os.path.join(user_home, - ".codechecker.passwords.json") - LOG.debug(session_cfg_file) - - scfg_dict = load_server_cfg(session_cfg_file) - - if not scfg_dict.get("credentials"): - scfg_dict["credentials"] = {} - - self.__save = scfg_dict - self.__autologin = scfg_dict.get("client_autologin") \ - if "client_autologin" in scfg_dict else True - - # Check and load token storage for user - self.token_file = os.path.join(user_home, ".codechecker.session.json") - LOG.debug(self.token_file) - - if os.path.exists(self.token_file): - with open(self.token_file, 'r') as f: - input = json.loads(f.read()) - self.__tokens = input.get("tokens") - check_file_owner_rw(self.token_file) - else: - with open(self.token_file, 'w') as f: - json.dump({'tokens': {}}, f) - os.chmod(self.token_file, stat.S_IRUSR | stat.S_IWUSR) - - self.__tokens = {} - - def is_autologin_enabled(self): - return self.__autologin - - def getToken(self, host, port): - return self.__tokens.get("{0}:{1}".format(host, port)) - - def getAuthString(self, host, port): - ret = self.__save["credentials"].get("{0}:{1}".format(host, port)) - if not ret: - ret = self.__save["credentials"].get(host) - if not ret: - ret = self.__save["credentials"].get("*:{0}".format(port)) - if not ret: - ret = self.__save["credentials"].get("*") - - return ret - - def saveToken(self, host, port, token, destroy=False): - if destroy: - del self.__tokens["{0}:{1}".format(host, port)] - else: - self.__tokens["{0}:{1}".format(host, port)] = token - - with open(self.token_file, 'w') as scfg: - portalocker.lock(scfg, portalocker.LOCK_EX) - json.dump({'tokens': self.__tokens}, scfg, - indent=2, sort_keys=True) - portalocker.unlock(scfg) diff --git a/libcodechecker/util.py b/libcodechecker/util.py index 116201e9fb..385939bcbe 100644 --- a/libcodechecker/util.py +++ b/libcodechecker/util.py @@ -14,6 +14,7 @@ import re import shutil import socket +import stat import subprocess import tempfile @@ -436,6 +437,28 @@ def get_new_line_col_without_whitespace(line_content, old_col): old_col - line_strip_len +def check_file_owner_rw(file_to_check): + """ + Check the file permissions. + Return: + True if only the owner can read or write the file. + False if other users or groups can read or write the file. + """ + + mode = os.stat(file_to_check)[stat.ST_MODE] + if mode & stat.S_IRGRP \ + or mode & stat.S_IWGRP \ + or mode & stat.S_IROTH \ + or mode & stat.S_IWOTH: + LOG.warning("'{0}' is readable by users other than you! " + "This poses a risk of leaking sensitive " + "information, such as passwords, session tokens, etc.!\n" + "Please 'chmod 0600 {0}' so only you can access the file." + .format(file_to_check)) + return False + return True + + def load_json_or_empty(path, default=None, kind=None): """ Load the contents of the given file as a JSON and return it's value, diff --git a/libcodechecker/version.py b/libcodechecker/version.py index 4b95d4fc6f..68c7d02b01 100644 --- a/libcodechecker/version.py +++ b/libcodechecker/version.py @@ -4,19 +4,22 @@ # License. See LICENSE.TXT for details. # ------------------------------------------------------------------------- """ -This module stores a global constant between CodeChecker server and client, -which dictates what API version the client should use, and what the server -accepts. +This module stores constants that are shared between the CodeChecker server +and client, related to API and other version-specific information. """ -# This dict object stores for each MAJOR version (key) the largest MINOR -# version (value) supported by the build. +# The name of the cookie which contains the user's authentication session's +# token. +SESSION_COOKIE_NAME = "__ccPrivilegedAccessToken" + +# The newest supported minor version (value) for each supported major version +# (key) in this particular build. SUPPORTED_VERSIONS = { 6: 6 } -# This value is automatically generated to represent the highest version -# available in the current build. +# Used by the client to automatically identify the latest major and minor +# version. CLIENT_API = '{0}.{1}'.format( max(SUPPORTED_VERSIONS.keys()), SUPPORTED_VERSIONS[max(SUPPORTED_VERSIONS.keys())]) diff --git a/tests/libtest/thrift_client_to_db.py b/tests/libtest/thrift_client_to_db.py index b8f062f5df..2b3b86aa46 100644 --- a/tests/libtest/thrift_client_to_db.py +++ b/tests/libtest/thrift_client_to_db.py @@ -5,7 +5,6 @@ # ----------------------------------------------------------------------------- from functools import partial -from libcodechecker import util import os import re import socket @@ -17,6 +16,7 @@ import shared +from libcodechecker import util from libcodechecker.version import CLIENT_API as VERSION @@ -110,9 +110,10 @@ class CCViewerHelper(ThriftAPIHelper): def __init__(self, protocol, host, port, product, endpoint, auto_handle_connection=True, session_token=None): # Import only if necessary; some tests may not add this to PYTHONPATH. - from libcodechecker import session_manager from codeCheckerDBAccess_v6 import codeCheckerDBAccess from codeCheckerDBAccess_v6.constants import MAX_QUERY_SIZE + from libcodechecker.libclient.credential_manager \ + import SESSION_COOKIE_NAME self.max_query_size = MAX_QUERY_SIZE url = util.create_product_url(protocol, host, port, @@ -123,8 +124,7 @@ def __init__(self, protocol, host, port, product, endpoint, protocol = TJSONProtocol.TJSONProtocol(transport) client = codeCheckerDBAccess.Client(protocol) if session_token: - headers = {'Cookie': session_manager.SESSION_COOKIE_NAME + - "=" + session_token} + headers = {'Cookie': SESSION_COOKIE_NAME + '=' + session_token} transport.setCustomHeaders(headers) super(CCViewerHelper, self).__init__(transport, client, auto_handle_connection) @@ -167,16 +167,16 @@ class CCAuthHelper(ThriftAPIHelper): def __init__(self, proto, host, port, uri, auto_handle_connection=True, session_token=None): # Import only if necessary; some tests may not add this to PYTHONPATH. - from libcodechecker import session_manager from Authentication_v6 import codeCheckerAuthentication + from libcodechecker.libclient.credential_manager \ + import SESSION_COOKIE_NAME url = util.create_product_url(proto, host, port, '/v' + VERSION + uri) transport = THttpClient.THttpClient(url) protocol = TJSONProtocol.TJSONProtocol(transport) client = codeCheckerAuthentication.Client(protocol) if session_token: - headers = {'Cookie': session_manager.SESSION_COOKIE_NAME + - "=" + session_token} + headers = {'Cookie': SESSION_COOKIE_NAME + '=' + session_token} transport.setCustomHeaders(headers) super(CCAuthHelper, self).__init__(transport, client, auto_handle_connection) @@ -191,8 +191,9 @@ def __init__(self, proto, host, port, product, uri, auto_handle_connection=True, session_token=None): # Import only if necessary; some tests may not add this to PYTHONPATH. - from libcodechecker import session_manager from ProductManagement_v6 import codeCheckerProductService + from libcodechecker.libclient.credential_manager \ + import SESSION_COOKIE_NAME full_uri = '/v' + VERSION + uri if product: full_uri = '/' + product + full_uri @@ -202,8 +203,7 @@ def __init__(self, proto, host, port, product, protocol = TJSONProtocol.TJSONProtocol(transport) client = codeCheckerProductService.Client(protocol) if session_token: - headers = {'Cookie': session_manager.SESSION_COOKIE_NAME + - "=" + session_token} + headers = {'Cookie': SESSION_COOKIE_NAME + '=' + session_token} transport.setCustomHeaders(headers) super(CCProductHelper, self).__init__(transport, client, auto_handle_connection)