remove passing session-id

This commit is contained in:
Tomas Kopecek 2022-11-24 14:00:30 +01:00
parent a1940470cf
commit dcd009e593
3 changed files with 86 additions and 72 deletions

View file

@ -7,5 +7,6 @@ BEGIN;
UPDATE archivetypes SET extensions = 'vhdx.gz vhdx.xz' WHERE name = 'vhdx-compressed';
-- for tag if session is closed or not
ALTER TABLE sessions ADD COLUMN closed BOOLEAN NOT NULL DEFAULT 'false';
ALTER TABLE sessions ADD COLUMN closed BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE sessions ADD CONSTRAINT no_closed_exclusive CHECK (closed IS FALSE OR "exclusive" IS NULL);
COMMIT;

View file

@ -2483,14 +2483,12 @@ class ClientSession(object):
self.callnum = None
# do we need to do anything else here?
self.authtype = None
self.session_key = None
else:
self.logged_in = True
self.callnum = 0
self.session_key = sinfo['session-key']
self.sinfo = sinfo
def login(self, opts=None, session_key=None):
def login(self, opts=None, renew=False):
"""
Username/password based login method
@ -2500,8 +2498,8 @@ class ClientSession(object):
"""
# store calling parameters
self.auth_method = {'method': 'login', 'kwargs': {'opts': opts}}
sinfo = self.callMethod('login', self.opts['user'], self.opts['password'],
opts=opts, session_key=session_key)
sinfo = self.callMethod('login', self.opts['user'], self.opts['password'], opts=opts,
renew=renew)
if not sinfo:
return False
self.setSession(sinfo)
@ -2514,7 +2512,7 @@ class ClientSession(object):
return type(self)(self.baseurl, opts=self.opts, sinfo=sinfo, auth_method=self.auth_method)
def gssapi_login(self, principal=None, keytab=None, ccache=None,
proxyuser=None, proxyauthtype=None, session_key=None):
proxyuser=None, proxyauthtype=None, renew=False):
"""
GSSAPI/Kerberos login method
@ -2523,7 +2521,6 @@ class ClientSession(object):
:param str ccache: path to ccache file/dir
:param str proxyuser: name of proxied user (e.g. forwarding by web ui)
:param int proxyauthtype: AUTHTYPE used by proxied user (can be different from ours)
:param str session_key: used for session renewal
:returns bool True: success or raises exception
"""
if not reqgssapi:
@ -2535,7 +2532,7 @@ class ClientSession(object):
'method': 'gssapi_login',
'kwargs': {
'principal': principal, 'keytab': keytab, 'ccache': ccache, 'proxyuser': proxyuser,
'proxyauthtype': proxyauthtype, 'session_key': session_key
'proxyauthtype': proxyauthtype
}
}
# force https
@ -2578,7 +2575,7 @@ class ClientSession(object):
# will fail with a handshake failure, which is retried by default.
# For this case we're now using retry=False and test errors for
# this exact usecase.
kwargs = {'proxyuser': proxyuser, 'session_key': session_key}
kwargs = {'proxyuser': proxyuser, 'renew': renew}
if proxyauthtype is not None:
kwargs['proxyauthtype'] = proxyauthtype
for tries in range(self.opts.get('max_retries', 30)):
@ -2627,7 +2624,7 @@ class ClientSession(object):
return True
def ssl_login(self, cert=None, ca=None, serverca=None, proxyuser=None, proxyauthtype=None,
session_key=None):
renew=False):
"""
SSL cert based login
@ -2636,16 +2633,14 @@ class ClientSession(object):
:param str serverca: path for CA public cert, otherwise system-wide CAs are used
:param str proxyuser: name of proxied user (e.g. forwarding by web ui)
:param int proxyauthtype: AUTHTYPE used by proxied user (can be different from ours)
:param str session_key: used for session renewal
:returns bool: success
"""
# store calling parameters
self.logger.error("ssl_login---------------")
self.auth_method = {
'method': 'ssl_login',
'kwargs': {
'cert': cert, 'ca': ca, 'serverca': serverca, 'proxyuser': proxyuser,
'proxyauthtype': proxyauthtype, 'session_key': session_key,
'cert': cert, 'ca': ca, 'serverca': serverca,
'proxyuser': proxyuser, 'proxyauthtype': proxyauthtype,
}
}
cert = cert or self.opts.get('cert')
@ -2676,7 +2671,7 @@ class ClientSession(object):
self.opts['serverca'] = serverca
e_str = None
try:
kwargs = {'proxyuser': proxyuser, 'session_key': session_key}
kwargs = {'proxyuser': proxyuser, 'renew': renew}
if proxyauthtype is not None:
kwargs['proxyauthtype'] = proxyauthtype
sinfo = self._callMethod('sslLogin', [], kwargs)
@ -2768,24 +2763,28 @@ class ClientSession(object):
return self._prepUpload(*args, **kwargs)
args = encode_args(*args, **kwargs)
headers = []
if self.logged_in:
sinfo = None
if getattr(self, 'sinfo') is not None:
# session renewal (not logged in, but have session data)
# makes sense only for new method/server
sinfo = self.sinfo.copy()
sinfo['callnum'] = self.callnum
self.callnum += 1
if sinfo.get('header-auth'):
handler = self.baseurl
headers += [
('Koji-Session-Id', str(self.sinfo['session-id'])),
('Koji-Session-Key', str(self.sinfo['session-key'])),
('Koji-Session-Callnum', str(sinfo['callnum'])),
]
else:
# old server
handler = "%s?%s" % (self.baseurl, six.moves.urllib.parse.urlencode(sinfo))
elif name == 'sslLogin':
headers += [
('Koji-Session-Id', str(sinfo['session-id'])),
('Koji-Session-Key', str(sinfo['session-key'])),
('Koji-Session-Callnum', str(sinfo['callnum'])),
]
if self.logged_in and not self.sinfo.get('header-auth'):
# old server
handler = "%s?%s" % (self.baseurl, six.moves.urllib.parse.urlencode(sinfo))
elif name in 'sslLogin':
handler = self.baseurl + '/ssllogin'
else:
handler = self.baseurl
request = dumps(args, name, allow_none=1)
if six.PY3:
# For python2, dumps() without encoding specified means return a str
@ -2898,12 +2897,11 @@ class ClientSession(object):
"""Renew expirated session or subsession."""
if not hasattr(self, 'auth_method'):
raise GenericError("Missing info for reauthentication")
# will be deleted by setSession
auth_method = getattr(self, self.auth_method['method'])
args = self.auth_method.get('args', [])
kwargs = self.auth_method.get('kwargs', {})
kwargs['session_key'] = self.session_key
self.setSession(None)
kwargs['renew'] = True
self.logged_in = False
auth_method(*args, **kwargs)
if self.exclusive:
self.exclusiveSession()

View file

@ -56,6 +56,8 @@ RetryWhitelist = [
'repoProblem',
]
AUTH_METHODS = ['login', 'sslLogin']
logger = logging.getLogger('koji.auth')
@ -82,8 +84,8 @@ class Session(object):
args = environ.get('QUERY_STRING', '')
# prefer new header-based sessions
if 'HTTP_KOJI_SESSION_ID' in environ:
id = int(environ['HTTP_KOJI_SESSION_ID'])
key = environ['HTTP_KOJI_SESSION_KEY']
self.id = int(environ['HTTP_KOJI_SESSION_ID'])
self.key = environ['HTTP_KOJI_SESSION_KEY']
try:
callnum = int(environ['HTTP_KOJI_CALLNUM'])
except KeyError:
@ -96,8 +98,8 @@ class Session(object):
return
args = urllib.parse.parse_qs(args, strict_parsing=True)
try:
id = int(args['session-id'][0])
key = args['session-key'][0]
self.id = int(args['session-id'][0])
self.key = args['session-key'][0]
except KeyError as field:
raise koji.AuthError('%s not specified in session args' % field)
try:
@ -119,23 +121,26 @@ class Session(object):
query = QueryProcessor(tables=['sessions'], columns=columns, aliases=aliases,
clauses=['id = %(id)i', 'key = %(key)s', 'hostip = %(hostip)s'],
values={'id': id, 'key': key, 'hostip': hostip},
values={'id': self.id, 'key': self.key, 'hostip': hostip},
opts={'rowlock': True})
session_data = query.executeOne(strict=False)
if not session_data:
query = QueryProcessor(tables=['sessions'], columns=['key', 'hostip'],
clauses=['id = %(id)i'], values={'id': id})
clauses=['id = %(id)i'], values={'id': self.id})
row = query.executeOne(strict=False)
if row:
if key != row['key']:
logger.warning("Session ID %s is not related to session key %s.", id, key)
if self.key != row['key']:
logger.warning("Session ID %s is not related to session key %s.",
self.id, self.key)
elif hostip != row['hostip']:
logger.warning("Session ID %s is not related to host IP %s.", id, hostip)
logger.warning("Session ID %s is not related to host IP %s.", self.id, hostip)
raise koji.AuthError('Invalid session or bad credentials')
# check for expiration
if session_data['expired']:
raise koji.AuthExpired('session "%i" has expired' % id)
if getattr(context, 'method') not in AUTH_METHODS:
raise koji.AuthExpired(f'session "{self.id}" has expired')
# check for callnum sanity
if callnum is not None:
try:
@ -145,8 +150,7 @@ class Session(object):
lastcall = session_data['callnum']
if lastcall is not None:
if lastcall > callnum:
raise koji.SequenceError("%d > %d (session %d)"
% (lastcall, callnum, id))
raise koji.SequenceError(f"{lastcall} > {callnum} (session {self.id})")
elif lastcall == callnum:
# Some explanation:
# This function is one of the few that performs its own commit.
@ -159,8 +163,11 @@ class Session(object):
method = getattr(context, 'method', 'UNKNOWN')
if method not in RetryWhitelist:
raise koji.RetryError(
"unable to retry call %d (method %s) for session %d"
% (callnum, method, id))
f"unable to retry call {callnum} "
f"(method {method}) for session {self.id}")
if session_data['expired']:
return
# read user data
# historical note:
@ -200,21 +207,19 @@ class Session(object):
# update timestamp
update = UpdateProcessor('sessions', rawdata={'update_time': 'NOW()'},
clauses=['id = %(id)i'], values={'id': id})
clauses=['id = %(id)i'], values={'id': self.id})
update.execute()
context.cnx.commit()
# update callnum (this is deliberately after the commit)
# see earlier note near RetryError
if callnum is not None:
update = UpdateProcessor('sessions', data={'callnum': callnum},
clauses=['id = %(id)i'], values={'id': id})
clauses=['id = %(id)i'], values={'id': self.id})
update.execute()
# we only want to commit the callnum change if there are other commits
context.commit_pending = False
# record the login data
self.id = id
self.key = key
self.hostip = hostip
self.callnum = callnum
self.user_id = session_data['user_id']
@ -327,7 +332,7 @@ class Session(object):
return (local_ip, local_port, remote_ip, remote_port)
def sslLogin(self, proxyuser=None, proxyauthtype=None, session_key=None):
def sslLogin(self, proxyuser=None, proxyauthtype=None, renew=False):
"""Login into brew via SSL. proxyuser name can be specified and if it is
allowed in the configuration file then connection is allowed to login as
@ -405,7 +410,7 @@ class Session(object):
hostip = self.get_remote_ip()
sinfo = self.createSession(user_id, hostip, authtype, session_key=session_key)
sinfo = self.createSession(user_id, hostip, authtype, renew=renew)
return sinfo
def makeExclusive(self, force=False):
@ -485,37 +490,48 @@ class Session(object):
update.execute()
context.cnx.commit()
def createSession(self, user_id, hostip, authtype, master=None, session_key=None):
def createSession(self, user_id, hostip, authtype, master=None, renew=False):
"""Create a new session for the given user.
Return a map containing the session-id and session-key.
If master is specified, create a subsession
"""
if session_key:
if master:
raise koji.GenericError("Can't call createSession with both master + session_key.")
query = QueryProcessor(tables=['sessions'], columns=['master'],
clauses=['key=%(session_key)d', 'closed=FALSE'],
values={'session_key': session_key})
row = query.executeOne(strict=False)
if not row:
raise koji.GenericError("Don't allow to renew non-existent or logged out session")
master = row['master']
# generate a random key
alnum = string.ascii_letters + string.digits
key = "%s-%s" % (user_id,
''.join([random.choice(alnum) for x in range(1, 20)]))
# use sha? sha.new(phrase).hexdigest()
# get a session id
session_id = nextval('sessions_id_seq')
if renew and self.id is not None:
# just update key
session_id = self.id
self.key = key
if self.master:
# check if master session died meanwhile
query = QueryProcessor(tables=['sessions'],
clauses=['id = %(master_id)d',
'expired IS FALSE',
'closed IS FALSE'],
values={'master_id': self.master},
opts={'countOnly': True})
if query.executeOne() == 0:
return None
# add session id to database
insert = InsertProcessor('sessions',
data={'id': session_id, 'user_id': user_id, 'key': key,
'hostip': hostip, 'authtype': authtype, 'master': master})
insert.execute()
update = UpdateProcessor('sessions',
clauses=['id=%(id)i'],
rawdata={'update_time': 'NOW()'},
data={'key': self.key, 'expired': False},
values={'id': self.id})
update.execute()
else:
# get a session id
session_id = nextval('sessions_id_seq')
# add session id to database
insert = InsertProcessor('sessions',
data={'id': session_id, 'user_id': user_id, 'key': key,
'hostip': hostip, 'authtype': authtype,
'master': master})
insert.execute()
context.cnx.commit()
# return session info
@ -532,8 +548,7 @@ class Session(object):
master = self.master
if master is None:
master = self.id
return self.createSession(self.user_id, self.hostip, self.authtype,
master=master)
return self.createSession(self.user_id, self.hostip, self.authtype, master=master)
def getPerms(self):
if not self.logged_in: