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

Removed forced eviction of locks #121

Merged
merged 1 commit into from
May 22, 2023
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
17 changes: 4 additions & 13 deletions src/core/wopi.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,19 +273,10 @@ def setLock(fileid, reqheaders, acctok):
if not retrievedLock or not utils.compareWopiLocks(retrievedLock, (oldLock if oldLock else lock)):
# lock mismatch, the WOPI client is supposed to acknowledge the existing lock to start a collab session,
# or deny access to the file in edit mode otherwise
evicted = False
if 'forcelock' in acctok and retrievedLock != 'External':
# here we try to evict the existing lock, and if possible we let the user go:
# this is to work around an issue with the Microsoft cloud!
evicted = utils.checkAndEvictLock(acctok['userid'], acctok['appname'], retrievedLock, oldLock, lock,
acctok['endpoint'], fn, int(statInfo['mtime']))
if evicted:
return utils.makeLockSuccessResponse(op, acctok, lock, oldLock, f"v{statInfo['etag']}")
else:
return utils.makeConflictResponse(op, acctok['userid'], retrievedLock, lock, oldLock, fn,
'The file is locked by %s' %
(lockHolder if lockHolder else 'another editor'),
savetime=savetime)
return utils.makeConflictResponse(op, acctok['userid'], retrievedLock, lock, oldLock, fn,
'The file is locked by %s' %
(lockHolder if lockHolder else 'another editor'),
savetime=savetime)

# else it's our own lock, refresh it (rechecking the oldLock if necessary, for atomicity) and return
try:
Expand Down
53 changes: 3 additions & 50 deletions src/core/wopiutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ def randomString(size):
return ''.join([choice(ascii_lowercase) for _ in range(size)])


def generateAccessToken(userid, fileid, viewmode, user, folderurl, endpoint, app, forcelock=False):
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
Expand Down Expand Up @@ -250,15 +250,13 @@ def generateAccessToken(userid, fileid, viewmode, user, folderurl, endpoint, app
if statinfo['size'] == 0:
# override preview mode when a new file is being created
viewmode = ViewMode.READ_WRITE
if forcelock:
tokmd['forcelock'] = '1'
acctok = jwt.encode(tokmd, srv.wopisecret, algorithm='HS256')
if 'MS 365' in appname:
srv.allusers.add(userid)
log.info('msg="Access token generated" userid="%s" wopiuser="%s" friendlyname="%s" usertype="%s" mode="%s" '
'endpoint="%s" filename="%s" inode="%s" mtime="%s" folderurl="%s" appname="%s"%s expiration="%d" token="%s"' %
'endpoint="%s" filename="%s" inode="%s" mtime="%s" folderurl="%s" appname="%s" expiration="%d" token="%s"' %
(userid[-20:], wopiuser, friendlyname, usertype, viewmode, endpoint, statinfo['filepath'], statinfo['inode'],
statinfo['mtime'], folderurl, appname, ' forcelock="True"' if forcelock else '', exptime, acctok[-20:]))
statinfo['mtime'], folderurl, appname, exptime, acctok[-20:]))
return statinfo['inode'], acctok, viewmode


Expand Down Expand Up @@ -380,51 +378,6 @@ def compareWopiLocks(lock1, lock2):
return False


def checkAndEvictLock(user, appname, retrievedlock, oldlock, lock, endpoint, filename, savetime):
'''Checks if the current lock can be evicted to overcome issue with Microsoft 365 cloud'''
evictlocktime = srv.config.get('general', 'evictlocktime', fallback='')
try:
evictlocktime = int(evictlocktime)
except ValueError:
return False
session = flask.request.headers.get('X-WOPI-SessionId')
if savetime > time.time() - evictlocktime:
# file is being edited, don't evict existing lock
log.warning('msg="File is actively edited, force-unlock prevented" lockop="%s" user="%s" '
'filename="%s" fileage="%1.1f" token="%s" sessionId="%s"' %
(('UnlockAndRelock' if oldlock else 'Lock'), user, filename, (time.time() - int(savetime)),
flask.request.args['access_token'][-20:], session))
if session:
srv.conflictsessions['failedtotakeover'][session] = {
'time': int(time.time()),
'user': user
}
return False
# ok, remove current lock and set new one
try:
st.refreshlock(endpoint, filename, user, appname, encodeLock(lock), encodeLock(retrievedlock))
except IOError as e:
log.error('msg="Failed to force a refreshlock" user="%s" filename="%s" error="%s" token="%s" sessionId="%s"' %
(user, filename, e, flask.request.args['access_token'][-20:], session))
return False
# and note the stealer and the evicted sessions
if session not in srv.conflictsessions['tookover']:
try:
formersession = json.loads(retrievedlock)['S']
except (TypeError, ValueError, KeyError):
formersession = retrievedlock
srv.conflictsessions['tookover'][session] = {
'time': int(time.time()),
'user': user,
'former': formersession
}
log.warning('msg="Former session was evicted" lockop="%s" user="%s" filename="%s" fileage="%1.1f" '
'formerlock="%s" token="%s" newsession="%s"' %
(('UnlockAndRelock' if oldlock else 'Lock'), user, filename, (time.time() - int(savetime)),
retrievedlock, flask.request.args['access_token'][-20:], session))
return True


def makeConflictResponse(operation, user, retrievedlock, lock, oldlock, filename, reason, savetime=None):
'''Generates and logs an HTTP 409 response in case of locks conflict'''
resp = flask.Response(mimetype='application/json')
Expand Down
4 changes: 1 addition & 3 deletions src/wopiserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,6 @@ def iopOpenInApp():
- string appurl: the URL of the end-user application
- string appviewurl (optional): the URL of the end-user application in view mode when different (defaults to appurl)
- string appinturl (optional): the internal URL of the end-user application (applicable with containerized deployments)
- string forcelock (optional): if present, will force the lock when possible to work around MS Office issues
- string usertype (optional): one of "regular", "federated", "ocm", "anonymous". Defaults to "regular"

Returns: a JSON response as follows:
Expand Down Expand Up @@ -326,7 +325,6 @@ def iopOpenInApp():
appname = url_unquote_plus(req.args.get('appname', ''))
appurl = url_unquote_plus(req.args.get('appurl', '')).strip('/')
appviewurl = url_unquote_plus(req.args.get('appviewurl', appurl)).strip('/')
forcelock = req.args.get('forcelock', False)
try:
usertype = utils.UserType(req.args.get('usertype', utils.UserType.REGULAR))
except (KeyError, ValueError) as e:
Expand All @@ -340,7 +338,7 @@ def iopOpenInApp():
try:
userid, wopiuser = storage.getuseridfromcreds(usertoken, wopiuser)
inode, acctok, vm = utils.generateAccessToken(userid, fileid, viewmode, (username, wopiuser, usertype), folderurl,
endpoint, (appname, appurl, appviewurl), forcelock=forcelock)
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"' %
Expand Down
7 changes: 0 additions & 7 deletions wopiserver.conf
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,6 @@ wopilockexpiration = 1800
# on-premise setups.
#wopilockstrictcheck = True

# Enable the ability to force-unlock sessions to overcome issues with Microsoft Office:
# if a session (access token) includes the option to override a previous lock, and the
# corresponding file was saved more than the given evictlocktime seconds before the request
# for lock, the previous lock is force-unlocked in order to grant a lock to the new
# session. By default this behavior is disabled.
#evictlocktime =

# Enable support of rename operations from WOPI apps. This is currently
# disabled by default because the implementation is not complete,
# and it is to be enabled for testing purposes only for the time being.
Expand Down