Skip to content

Commit

Permalink
Completed transition to new discovery logic
Browse files Browse the repository at this point in the history
  • Loading branch information
glpatcern committed Jun 25, 2021
1 parent 23d8c3e commit 214cf6a
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 47 deletions.
8 changes: 5 additions & 3 deletions src/bridge/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,9 @@ def appopen():
WB.openfiles[wopisrc]['toclose'] = wopilock['toclose']
else:
WB.openfiles[wopisrc] = {'acctok': acctok, 'tosave': False,
'lastsave': int(time.time()) - WB.saveinterval,
'toclose': {acctok[-20:]: False},
'docid': wopilock['docid'],
'lastsave': int(time.time()) - WB.saveinterval,
'toclose': {acctok[-20:]: False},
'docid': wopilock['docid'],
}
# also clear any potential stale response for this document
try:
Expand Down Expand Up @@ -203,6 +203,8 @@ def appsave(docid):
wopisrc = meta[:meta.index('?t=')]
acctok = meta[meta.index('?t=')+3:]
isclose = flask.request.args.get('close') == 'true'
if not docid:
raise ValueError
WB.log.info('msg="Save: requested action" isclose="%s" docid="%s" wopisrc="%s" token="%s"' %
(isclose, docid, wopisrc, acctok[-20:]))
except (KeyError, ValueError) as e:
Expand Down
34 changes: 17 additions & 17 deletions src/core/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
# convenience references to global entities
srv = None
log = None

apps = {}

def registerapp(appname, appurl, appinturl):
'''Registers the given app in the internal endpoints list'''
Expand All @@ -34,21 +34,21 @@ def registerapp(appname, appurl, appinturl):
urlsrc = discXml.find('net-zone/app')[0].attrib['urlsrc']
if urlsrc.find('loleaflet') > 0:
# this is Collabora
srv.apps[appname] = {}
apps[appname] = {}
codetypes = srv.config.get('general', 'codeofficetypes', fallback='.odt .ods .odp').split()
for t in codetypes:
srv.endpoints[t] = {}
srv.endpoints[t]['view'] = urlsrc + 'permission=readonly'
srv.endpoints[t]['edit'] = urlsrc + 'permission=edit'
srv.endpoints[t]['new'] = urlsrc + 'permission=edit' # pylint: disable=bad-whitespace
srv.apps[appname][t] = srv.endpoints[t]
apps[appname][t] = srv.endpoints[t]
log.info('msg="Collabora Online endpoints successfully configured" count="%d" CODEURL="%s"' %
(len(codetypes), srv.endpoints['.odt']['edit']))
return flask.Response(json.dumps(list(srv.apps[appname].keys())), mimetype='application/json')
return flask.Response(json.dumps(list(apps[appname].keys())), mimetype='application/json')

# else this must be Microsoft Office Online
# TODO remove hardcoded logic
srv.apps[appname] = {}
apps[appname] = {}
srv.endpoints['.docx'] = {}
srv.endpoints['.docx']['view'] = appurl + '/wv/wordviewerframe.aspx?edit=0'
srv.endpoints['.docx']['edit'] = appurl + '/we/wordeditorframe.aspx?edit=1'
Expand All @@ -61,12 +61,12 @@ def registerapp(appname, appurl, appinturl):
srv.endpoints['.pptx']['view'] = appurl + '/p/PowerPointFrame.aspx?PowerPointView=ReadingView'
srv.endpoints['.pptx']['edit'] = appurl + '/p/PowerPointFrame.aspx?PowerPointView=EditView'
srv.endpoints['.pptx']['new'] = appurl + '/p/PowerPointFrame.aspx?PowerPointView=EditView&New=1' # pylint: disable=bad-whitespace
srv.apps[appname]['.docx'] = srv.endpoints['.docx']
srv.apps[appname]['.xlsx'] = srv.endpoints['.xlsx']
srv.apps[appname]['.pptx'] = srv.endpoints['.pptx']
apps[appname]['.docx'] = srv.endpoints['.docx']
apps[appname]['.xlsx'] = srv.endpoints['.xlsx']
apps[appname]['.pptx'] = srv.endpoints['.pptx']
log.info('msg="Microsoft Office Online endpoints successfully configured" OfficeURL="%s"' %
srv.endpoints['.docx']['edit'])
return flask.Response(json.dumps(list(srv.apps[appname].keys())), mimetype='application/json')
return flask.Response(json.dumps(list(apps[appname].keys())), mimetype='application/json')

elif discReq.status_code == http.client.NOT_FOUND:
# try and scrape the app homepage to see if a bridge-supported app is found
Expand All @@ -75,30 +75,30 @@ def registerapp(appname, appurl, appinturl):
if discReq.find('CodiMD') > 0:
# TODO remove hardcoded logic
bridge.WB.loadplugin(appname, appurl, appinturl)
srv.apps[appname] = {}
apps[appname] = {}
bridgeurl = srv.config.get('general', 'wopiurl') + '/wopi/bridge/open?'
srv.endpoints['.md'] = {}
srv.endpoints['.md']['view'] = srv.endpoints['.md']['edit'] = bridgeurl
srv.endpoints['.zmd'] = {}
srv.endpoints['.zmd']['view'] = srv.endpoints['.zmd']['edit'] = bridgeurl
srv.endpoints['.txt'] = {}
srv.endpoints['.txt']['view'] = srv.endpoints['.txt']['edit'] = bridgeurl
srv.apps[appname]['.md'] = srv.endpoints['.md']
srv.apps[appname]['.zmd'] = srv.endpoints['.zmd']
srv.apps[appname]['.txt'] = srv.endpoints['.txt']
apps[appname]['.md'] = srv.endpoints['.md']
apps[appname]['.zmd'] = srv.endpoints['.zmd']
apps[appname]['.txt'] = srv.endpoints['.txt']
log.info('msg="iopRegisterApp: CodiMD endpoints successfully configured" BridgeURL="%s"' % bridgeurl)
return flask.Response(json.dumps(list(srv.apps[appname].keys())), mimetype='application/json')
return flask.Response(json.dumps(list(apps[appname].keys())), mimetype='application/json')

if discReq.find('Etherpad') > 0:
bridge.WB.loadplugin(appname, appurl, appinturl)
bridgeurl = srv.config.get('general', 'wopiurl') + '/wopi/bridge/open?'
# TODO remove hardcoded logic
srv.apps[appname] = {}
apps[appname] = {}
srv.endpoints['.epd'] = {}
srv.endpoints['.epd']['view'] = srv.endpoints['.epd']['edit'] = bridgeurl
srv.apps[appname]['.epd'] = srv.endpoints['.epd']
apps[appname]['.epd'] = srv.endpoints['.epd']
log.info('msg="iopRegisterApp: Etherpad endpoints successfully configured" BridgeURL="%s"' % bridgeurl)
return flask.Response(json.dumps(list(srv.apps[appname].keys())), mimetype='application/json')
return flask.Response(json.dumps(list(apps[appname].keys())), mimetype='application/json')
except ValueError:
# bridge plugin could not be initialized
return 'Failed to initialize WOPI bridge plugin for app "%s"' % appname, http.client.INTERNAL_SERVER_ERROR
Expand Down
41 changes: 25 additions & 16 deletions src/core/wopi.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from urllib.parse import quote_plus as url_quote_plus
from urllib.parse import unquote as url_unquote
import core.wopiutils as utils
import core.discovery

# convenience references to global entities
st = None
Expand Down Expand Up @@ -65,19 +66,23 @@ def checkFileInfo(fileid):
filemd['SupportsUpdate'] = filemd['UserCanWrite'] = filemd['SupportsLocks'] = filemd['SupportsRename'] = \
filemd['SupportsDeleteFile'] = filemd['UserCanRename'] = acctok['viewmode'] == utils.ViewMode.READ_WRITE
filemd['UserCanNotWriteRelative'] = acctok['viewmode'] != utils.ViewMode.READ_WRITE
if acctok['appname'] in core.discovery.apps:
appurl = core.discovery.apps[acctok['appname']][fExt]
else:
appurl = srv.endpoints[fExt] # TODO deprecated, must make sure appname is always correct
filemd['HostViewUrl'] = '%s&%s' % (appurl['view'], wopiSrc)
filemd['HostEditUrl'] = '%s&%s' % (appurl['edit'], wopiSrc)

# populate app-specific metadata
# the following properties are only used by MS Office Online
if fExt in ['.docx', '.xlsx', '.pptx']:
filemd['HostViewUrl'] = '%s&%s' % (srv.endpoints[fExt]['view'], wopiSrc)
filemd['HostEditUrl'] = '%s&%s' % (srv.endpoints[fExt]['edit'], wopiSrc)
if acctok['appname'].find('Microsoft') > 0:
# the following actions are broken in MS Office Online, therefore they are disabled
filemd['SupportsRename'] = filemd['UserCanRename'] = False
# the following is to enable the 'Edit in Word/Excel/PowerPoint' (desktop) action (probably broken)
try:
filemd['ClientUrl'] = srv.config.get('general', 'webdavurl') + '/' + acctok['filename']
except configparser.NoOptionError:
# if no WebDAV URL is provided, ignore this setting
pass
# the following is to enable the 'Edit in Word/Excel/PowerPoint' (desktop) action (probably broken)
try:
filemd['ClientUrl'] = srv.config.get('general', 'webdavurl') + '/' + acctok['filename']
except configparser.NoOptionError:
# if no WebDAV URL is provided, ignore this setting
pass
# extensions for Collabora Online
filemd['EnableOwnerTermination'] = True
filemd['DisableExport'] = filemd['DisableCopy'] = filemd['DisablePrint'] = acctok['viewmode'] == utils.ViewMode.VIEW_ONLY
Expand Down Expand Up @@ -305,18 +310,22 @@ 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'], targetName, acctok['username']))
inode, newacctok = utils.generateAccessToken(acctok['userid'], targetName, utils.ViewMode.READ_WRITE, acctok['username'], \
acctok['folderurl'], acctok['endpoint'], acctok['appname'])
inode, _, newacctok = utils.generateAccessToken(acctok['userid'], targetName, utils.ViewMode.READ_WRITE, acctok['username'], \
acctok['folderurl'], acctok['endpoint'], acctok['appname'])
# prepare and send the response as JSON
putrelmd = {}
putrelmd['Name'] = os.path.basename(targetName)
putrelmd['Url'] = '%s?access_token=%s' % (url_unquote(utils.generateWopiSrc(inode)), newacctok)
fExt = os.path.splitext(targetName)[1]
if fExt in srv.endpoints:
appurl = None
if acctok['appname'] in core.discovery.apps:
appurl = core.discovery.apps[acctok['appname']][fExt]
elif fExt in srv.endpoints:
appurl = srv.endpoints[fExt] # TODO deprecated
if appurl:
putrelmd['HostEditUrl'] = '%s&WOPISrc=%s&access_token=%s' % \
(srv.endpoints[fExt]['edit'], \
utils.generateWopiSrc(inode), newacctok)
#else we don't know the app to edit this file type, therefore we do not provide the info
(appurl['edit'], utils.generateWopiSrc(inode), newacctok)
# else we don't know the app to edit this file type, therefore we do not provide the info
log.debug('msg="PutRelative response" token="%s" metadata="%s"' % (newacctok[-20:], putrelmd))
return flask.Response(json.dumps(putrelmd), mimetype='application/json')

Expand Down
11 changes: 6 additions & 5 deletions src/core/wopiutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,14 +128,15 @@ def generateAccessToken(userid, fileid, viewmode, username, folderurl, endpoint,
# if write access is requested, probe whether there's already a lock file coming from Desktop applications
exptime = int(time.time()) + srv.tokenvalidity
acctok = jwt.encode({'userid': userid, 'filename': statInfo['filepath'], 'username': username,
'viewmode': viewmode.value, 'folderurl': folderurl, 'exp': exptime, 'endpoint': endpoint},
'viewmode': viewmode.value, 'folderurl': folderurl, 'endpoint': endpoint,
'appname': appname, 'exp': exptime},
srv.wopisecret, algorithm='HS256')
log.info('msg="Access token generated" userid="%s" mode="%s" endpoint="%s" filename="%s" inode="%s" ' \
'mtime="%s" folderurl="%s" expiration="%d" token="%s"' %
'mtime="%s" folderurl="%s" appname="%s" expiration="%d" token="%s"' %
(userid, viewmode, endpoint, statInfo['filepath'], statInfo['inode'], statInfo['mtime'], \
folderurl, exptime, acctok[-20:]))
# return the inode == fileid and the access token
return statInfo['inode'], acctok
folderurl, appname, exptime, acctok[-20:]))
# return the inode == fileid, the filepath and the access token
return statInfo['inode'], statInfo['filepath'], acctok


def getLockName(filename):
Expand Down
13 changes: 7 additions & 6 deletions src/wopiserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ class Wopi:
log = utils.JsonLogger(app.logger)
openfiles = {}
endpoints = {}
apps = {}

@classmethod
def init(cls):
Expand Down Expand Up @@ -256,12 +255,12 @@ def iopOpen():
endpoint = req.args.get('endpoint', 'default')
appname = urllib.parse.unquote(req.args.get('appname', 'default'))
try:
inode, acctok = utils.generateAccessToken(userid, fileid, viewmode, username, folderurl, endpoint, appname)
inode, fname, acctok = utils.generateAccessToken(userid, fileid, viewmode, username, folderurl, endpoint, appname)
# generate the URL-encoded payload for the app engine
url = '%s&access_token=%s' % (utils.generateWopiSrc(inode), acctok) # no need to URL-encode the JWT token
if appname == '':
if appname not in core.discovery.apps:
return url
return Wopi.apps[appname][os.path.splitext(filename)[1]]['edit' if viewmode == utils.ViewMode.READ_WRITE else 'view']
return flask.redirect('%s&WOPISrc=%s' % (core.discovery.apps[appname][os.path.splitext(fname)[1]]['edit' if viewmode == utils.ViewMode.READ_WRITE else 'view'], url))
except IOError as e:
Wopi.log.info('msg="iopOpen: remote error on generating token" client="%s" user="%s" ' \
'friendlyname="%s" mode="%s" endpoint="%s" reason="%s"' %
Expand Down Expand Up @@ -339,7 +338,7 @@ def iopDiscoverApp():


#
# The WOPI protocol implementation starts here
# WOPI protocol implementation
#
@Wopi.app.route("/wopi/files/<fileid>", methods=['GET'])
def wopiCheckFileInfo(fileid):
Expand Down Expand Up @@ -464,11 +463,13 @@ def bridgeOpen():


@Wopi.app.route("/wopi/bridge/<docid>", methods=["POST"])
@Wopi.metrics.do_not_track()
def bridgeSave(docid):
return bridge.appsave(docid)


@Wopi.app.route("/wopi/bridge/save", methods=["GET"])
@Wopi.metrics.do_not_track()
def bridgeSave_old():
docid = flask.request.args.get('id')
return bridge.appsave(docid)
Expand Down Expand Up @@ -517,5 +518,5 @@ def cboxDownload():
#
if __name__ == '__main__':
Wopi.init()
core.discovery.initappsregistry()
core.discovery.initappsregistry() # TODO to be removed
Wopi.run()

0 comments on commit 214cf6a

Please sign in to comment.