Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure the UserId WOPI property is consistent #48

Merged
merged 2 commits into from
Oct 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/core/cs3iface.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def stat(endpoint, fileid, userid, versioninv=0):
inode = urlsafe_b64encode(statInfo.info.id.opaque_id.encode())
return {'inode': statInfo.info.id.storage_id + '-' + inode.decode(),
'filepath': statInfo.info.path,
'userid': statInfo.info.owner.opaque_id,
'ownerid': statInfo.info.owner.opaque_id + '@' + statInfo.info.owner.idp,
'size': statInfo.info.size,
'mtime': statInfo.info.mtime.seconds
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/localiface.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def stat(_endpoint, filepath, _userid):
return {
'inode': str(statInfo.st_ino),
'filepath': filepath,
'userid': str(statInfo.st_uid) + ':' + str(statInfo.st_gid),
'ownerid': str(statInfo.st_uid) + ':' + str(statInfo.st_gid),
'size': statInfo.st_size,
'mtime': statInfo.st_mtime
}
Expand Down
12 changes: 6 additions & 6 deletions src/core/wopi.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ def checkFileInfo(fileid):
if acctok['viewmode'] in (utils.ViewMode.READ_ONLY, utils.ViewMode.READ_WRITE):
filemd['DownloadUrl'] = '%s?access_token=%s' % \
(srv.config.get('general', 'downloadurl'), flask.request.args['access_token'])
filemd['OwnerId'] = statInfo['userid']
filemd['UserId'] = acctok['userid'] # typically same as OwnerId; different when accessing shared documents
filemd['OwnerId'] = statInfo['ownerid']
filemd['UserId'] = acctok['wopiuser'] # typically same as OwnerId; different when accessing shared documents
filemd['Size'] = statInfo['size']
# TODO the version is generated like this in ownCloud: 'V' . $file->getEtag() . \md5($file->getChecksum());
filemd['Version'] = statInfo['mtime'] # mtime is used as version here
Expand Down Expand Up @@ -319,10 +319,10 @@ def putRelative(fileid, reqheaders, acctok):
log.info('msg="PutRelative: generating new access token" user="%s" filename="%s" ' \
'mode="ViewMode.READ_WRITE" friendlyname="%s"' %
(acctok['userid'][-20:], targetName, acctok['username']))
inode, newacctok = utils.generateAccessToken(acctok['userid'], targetName, \
utils.ViewMode.READ_WRITE, acctok['username'], \
acctok['folderurl'], acctok['endpoint'], acctok['appname'], \
acctok['appediturl'], acctok['appviewurl'])
inode, newacctok = utils.generateAccessToken(acctok['userid'], targetName, utils.ViewMode.READ_WRITE,
(acctok['username'], acctok['wopiuser']), \
acctok['folderurl'], acctok['endpoint'], \
(acctok['appname'], acctok['appediturl'], acctok['appviewurl']))
# prepare and send the response as JSON
putrelmd = {}
putrelmd['Name'] = os.path.basename(targetName)
Expand Down
13 changes: 8 additions & 5 deletions src/core/wopiutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
st = None
srv = None
log = None
endpoints = None
endpoints = {}

class ViewMode(Enum):
'''File view mode: reference is `ViewMode` at
Expand Down Expand Up @@ -117,9 +117,11 @@ def randomString(size):
return ''.join([choice(ascii_lowercase) for _ in range(size)])


def generateAccessToken(userid, fileid, viewmode, username, folderurl, endpoint, appname, appediturl, appviewurl):
def generateAccessToken(userid, fileid, viewmode, user, folderurl, endpoint, app):
'''Generates an access token for a given file and a given user, and returns a tuple with
the file's inode and the URL-encoded access token.'''
appname, appediturl, appviewurl = app
username, wopiuser = user
try:
# stat the file to check for existence and get a version-invariant inode and modification time:
# the inode serves as fileid (and must not change across save operations), the mtime is used for version information.
Expand All @@ -139,13 +141,14 @@ def generateAccessToken(userid, fileid, viewmode, username, folderurl, endpoint,
log.critical('msg="No app URLs registered for the given file type" fileext="%s" mimetypescount="%d"' %
(fext, len(endpoints) if endpoints else 0))
raise IOError
acctok = jwt.encode({'userid': userid, 'filename': statinfo['filepath'], 'username': username,
acctok = jwt.encode({'userid': userid, 'wopiuser': wopiuser, 'filename': statinfo['filepath'], 'username': username,
'viewmode': viewmode.value, 'folderurl': folderurl, 'endpoint': endpoint,
'appname': appname, 'appediturl': appediturl, 'appviewurl': appviewurl, 'exp': exptime},
srv.wopisecret, algorithm='HS256')
log.info('msg="Access token generated" userid="%s" mode="%s" endpoint="%s" filename="%s" inode="%s" ' \
log.info('msg="Access token generated" userid="%s" wopiuser="%s" mode="%s" endpoint="%s" filename="%s" inode="%s" ' \
'mtime="%s" folderurl="%s" appname="%s" expiration="%d" token="%s"' %
(userid[-20:], viewmode, endpoint, statinfo['filepath'], statinfo['inode'], statinfo['mtime'], \
(userid[-20:], wopiuser if wopiuser != userid else username, viewmode, endpoint, \
statinfo['filepath'], statinfo['inode'], statinfo['mtime'], \
folderurl, appname, exptime, acctok[-20:]))
# return the inode == fileid, the filepath and the access token
return statinfo['inode'], acctok
Expand Down
4 changes: 2 additions & 2 deletions src/core/xrootiface.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ def statx(endpoint, filepath, userid, versioninv=0):
log.debug('msg="Invoked stat return" inode="%s" filepath="%s"' % (inode, _getfilepath(filepath)))
return {'inode': inode,
'filepath': filepath,
'userid': statxdata[5] + ':' + statxdata[6],
'ownerid': statxdata[5] + ':' + statxdata[6],
'size': int(statxdata[8]),
'mtime': int(statxdata[12])}
# now stat the corresponding version folder to get an inode invariant to save operations, see CERNBOX-1216
Expand Down Expand Up @@ -189,7 +189,7 @@ def statx(endpoint, filepath, userid, versioninv=0):
log.debug('msg="Invoked stat return" inode="%s" filepath="%s"' % (inode, _getfilepath(verFolder)))
return {'inode': inode,
'filepath': filepath,
'userid': statxdata[5] + ':' + statxdata[6],
'ownerid': statxdata[5] + ':' + statxdata[6],
'size': int(statxdata[8]),
'mtime': int(statxdata[12])}

Expand Down
35 changes: 19 additions & 16 deletions src/wopiserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,10 @@ def iopOpenInApp():
- string fileid: the Reva fileid of the file to be opened
- string endpoint (optional): the storage endpoint to be used to look up the file or the storage id, in case of
multi-instance underlying storage; defaults to 'default'
- string username (optional): user's full display name, typically shown by the Office app
- string username (optional): user's full display name, typically shown by the app; defaults to
'Guest ' + 3 random letters to represent anonymous users
- string userid (optional): an unique identifier for the user, used internally by the app; defaults to
a random string of 10 characters to represent anonymous users
- string folderurl (optional): the URL to come back to the containing folder for this file, typically shown by the app
- string appname: the identifier of the end-user application to be served
- string appurl: the URL of the end-user application
Expand All @@ -239,7 +242,7 @@ def iopOpenInApp():
'client="%s" clientAuth="%s"' % (req.remote_addr, req.headers.get('Authorization')))
return UNAUTHORIZED
try:
userid = req.headers['TokenHeader']
usertoken = req.headers['TokenHeader']
except KeyError:
Wopi.log.warning('msg="iopOpenInApp: missing TokenHeader in request" client="%s"' % req.remote_addr)
return UNAUTHORIZED
Expand All @@ -256,6 +259,8 @@ def iopOpenInApp():
(req.remote_addr, req.args.get('viewmode'), e))
return 'Missing or invalid viewmode argument', http.client.BAD_REQUEST
username = req.args.get('username', '')
# this needs to be a unique identifier: if missing (case of anonymous users), just generate a random string
wopiuser = req.args.get('userid', utils.randomString(10))
folderurl = url_unquote(req.args.get('folderurl', '%2F')) # defaults to `/`
endpoint = req.args.get('endpoint', 'default')
appname = url_unquote(req.args.get('appname', ''))
Expand All @@ -277,12 +282,12 @@ def iopOpenInApp():
appurl = appviewurl = Wopi.wopiurl + '/wopi/bridge/open'

try:
inode, acctok = utils.generateAccessToken(userid, fileid, viewmode, username, folderurl, endpoint,
appname, appurl, appviewurl)
inode, acctok = utils.generateAccessToken(usertoken, fileid, viewmode, (username, wopiuser), folderurl, endpoint,
(appname, appurl, appviewurl))
except IOError as e:
Wopi.log.info('msg="iopOpenInApp: remote error on generating token" client="%s" user="%s" ' \
'friendlyname="%s" mode="%s" endpoint="%s" reason="%s"' %
(req.remote_addr, userid[-20:], username, viewmode, endpoint, e))
(req.remote_addr, usertoken[-20:], username, viewmode, endpoint, e))
return 'Remote error, file not found or file is a directory', http.client.NOT_FOUND

res = {}
Expand Down Expand Up @@ -540,16 +545,12 @@ def cboxOpen_deprecated():
return UNAUTHORIZED
# now validate the user identity and deny root access
try:
if 'TokenHeader' in req.headers:
userid = req.headers['TokenHeader']
else:
# backwards compatibility
userid = 'N/A'
ruid = int(req.args['ruid'])
rgid = int(req.args['rgid'])
userid = '%d:%d' % (ruid, rgid)
if ruid == 0 or rgid == 0:
raise ValueError
userid = 'N/A'
ruid = int(req.args['ruid'])
rgid = int(req.args['rgid'])
userid = '%d:%d' % (ruid, rgid)
if ruid == 0 or rgid == 0:
raise ValueError
except ValueError:
Wopi.log.warning('msg="cboxOpen: invalid or missing user/token in request" client="%s" user="%s"' %
(req.remote_addr, userid))
Expand All @@ -573,7 +574,9 @@ def cboxOpen_deprecated():
folderurl = url_unquote(req.args.get('folderurl', '%2F')) # defaults to `/`
endpoint = req.args.get('endpoint', 'default')
try:
inode, acctok = utils.generateAccessToken(userid, filename, viewmode, username, folderurl, endpoint, '', '', '')
# here we leave wopiuser empty as `userid` (i.e. uid:gid) is known to be consistent over time
inode, acctok = utils.generateAccessToken(userid, filename, viewmode, (username, userid), \
folderurl, endpoint, ('', '', ''))
except IOError as e:
Wopi.log.warning('msg="cboxOpen: remote error on generating token" client="%s" user="%s" ' \
'friendlyname="%s" mode="%s" endpoint="%s" reason="%s"' %
Expand Down