Skip to content

Commit

Permalink
Ensure the UserId WOPI property is consistent over multiple session…
Browse files Browse the repository at this point in the history
…s for a given user
  • Loading branch information
glpatcern committed Oct 11, 2021
1 parent 5ea0d7b commit 2901af7
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 27 deletions.
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,
'userid': statInfo.info.owner.opaque_id + '@' + statInfo.info.owner.idp,
'size': statInfo.info.size,
'mtime': statInfo.info.mtime.seconds
}
Expand Down
12 changes: 6 additions & 6 deletions src/core/wopi.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def checkFileInfo(fileid):
# encode the path part as it is going to be an URL GET argument
filemd['BreadcrumbFolderUrl'] = furl[:furl.find('=')+1] + url_quote_plus(furl[furl.find('=')+1:]) if furl != '/' else ''
if acctok['username'] == '':
filemd['UserFriendlyName'] = 'Guest ' + utils.randomString(3)
filemd['UserFriendlyName'] = 'Guest ' + (acctok['wopiuser'][:3] if acctok.get('wopiuser') else utils.randomString(3))
if '?path' in furl and furl[-1] != '/' and furl[-1] != '=':
# this is a subfolder of a public share, show it
filemd['BreadcrumbFolderName'] = 'Back to ' + furl[furl.find('?path'):].split('/')[-1]
Expand All @@ -61,7 +61,7 @@ def checkFileInfo(fileid):
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['UserId'] = acctok['wopiuser'] if acctok.get('wopiuser') else acctok['userid'] # 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
11 changes: 7 additions & 4 deletions src/core/wopiutils.py
Original file line number Diff line number Diff line change
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 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
34 changes: 18 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,8 @@ 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, ''), 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

0 comments on commit 2901af7

Please sign in to comment.