mirror of
https://github.com/Leseratte10/acsm-calibre-plugin.git
synced 2024-11-11 01:06:10 +06:00
Update loanID generation code
Should fix #31. Apparently I implemented the loanID code wrong, that sometimes caused book returns to fail (or even worse, return the wrong book) if you had multiple active loans from the same distributor. Also adds a test case to catch this bug should it ever occur again.
This commit is contained in:
parent
396f0cfad0
commit
c6b9e5c59b
|
@ -40,7 +40,7 @@ See the "LICENSE" file for a full copy of the GNU GPL v3.
|
||||||
|
|
||||||
## Known bugs
|
## Known bugs
|
||||||
|
|
||||||
- Returning an eBook to a library running Adobe Content Server 6 (ACS6) or newer will fail. The plugin will claim the book return was successful, but the book won't be marked as returned on the libraries' servers. This will hopefully be fixed with the next version of the plugin - I already dumped a bunch of logs from ADE so I know what my plugin is missing, now I just need to implement the fix and see if it works. It might be a good idea to stop returning books with the current version of the plugin, as that'd be a difference that the libraries could detect as doing something weird, if they're using ACS6.
|
- Versions 0.0.16 and below did sometimes return the wrong eBook (or none at all) when trying to return a book to the library through the "Loaned books" list, if you had multiple active loans from the same distributor / library. This will be fixed with 0.0.17.
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,9 @@
|
||||||
# fix broken URLs with missing protocol, fix loan data for loans without device ID,
|
# fix broken URLs with missing protocol, fix loan data for loans without device ID,
|
||||||
# fix nonce calculation yet again, merge #26 to make importing a WINE auth more reliable,
|
# fix nonce calculation yet again, merge #26 to make importing a WINE auth more reliable,
|
||||||
# update python-oscrypto to unofficial fork to fix OpenSSL 3 support.
|
# update python-oscrypto to unofficial fork to fix OpenSSL 3 support.
|
||||||
|
# In Progress:
|
||||||
|
# Fix bug that would sometimes return the wrong book (or none at all) if you had
|
||||||
|
# multiple active loans from the same distributor.
|
||||||
|
|
||||||
|
|
||||||
PLUGIN_NAME = "DeACSM"
|
PLUGIN_NAME = "DeACSM"
|
||||||
|
|
|
@ -482,32 +482,28 @@ def fulfill(acsm_file, do_notify = False):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def updateLoanReturnData(fulfillmentResultToken):
|
def updateLoanReturnData(fulfillmentResultToken, forceTestBehaviour=False):
|
||||||
|
|
||||||
NSMAP = { "adept" : "http://ns.adobe.com/adept" }
|
NSMAP = { "adept" : "http://ns.adobe.com/adept" }
|
||||||
adNS = lambda tag: '{%s}%s' % ('http://ns.adobe.com/adept', tag)
|
adNS = lambda tag: '{%s}%s' % ('http://ns.adobe.com/adept', tag)
|
||||||
dcNS = lambda tag: '{%s}%s' % ('http://purl.org/dc/elements/1.1/', tag)
|
dcNS = lambda tag: '{%s}%s' % ('http://purl.org/dc/elements/1.1/', tag)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
loanToken = fulfillmentResultToken.find("./%s" % (adNS("loanToken")))
|
fulfillment_id = fulfillmentResultToken.find("./%s/%s/%s/%s" % (adNS("fulfillmentResult"), adNS("resourceItemInfo"), adNS("licenseToken"), adNS("fulfillment"))).text
|
||||||
if (loanToken is None):
|
if (fulfillment_id is None):
|
||||||
print("Loan token not found")
|
print("Fulfillment ID not found, can't generate loan token")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
except:
|
except:
|
||||||
print("Loan token error")
|
print("Loan token error")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
operatorURL = loanToken.find("./%s" % (adNS("operatorURL"))).text
|
operatorURL = fulfillmentResultToken.find("./%s/%s/%s/%s" % (adNS("fulfillmentResult"), adNS("resourceItemInfo"), adNS("licenseToken"), adNS("operatorURL"))).text
|
||||||
except:
|
except:
|
||||||
print("OperatorURL missing")
|
print("OperatorURL missing")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
|
||||||
loanID = None
|
|
||||||
loanID = loanToken.findall("./%s" % (adNS("loan")))[0].text
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
book_name = fulfillmentResultToken.find("./%s/%s/%s/%s" % (adNS("fulfillmentResult"), adNS("resourceItemInfo"), adNS("metadata"), dcNS("title"))).text
|
book_name = fulfillmentResultToken.find("./%s/%s/%s/%s" % (adNS("fulfillmentResult"), adNS("resourceItemInfo"), adNS("metadata"), dcNS("title"))).text
|
||||||
|
|
||||||
|
@ -539,6 +535,18 @@ def updateLoanReturnData(fulfillmentResultToken):
|
||||||
# "loanID" is the loan ID
|
# "loanID" is the loan ID
|
||||||
# "validUntil" is how long it's valid
|
# "validUntil" is how long it's valid
|
||||||
|
|
||||||
|
new_loan_record = {
|
||||||
|
"book_name": book_name,
|
||||||
|
"user": userUUID,
|
||||||
|
"device": deviceUUID,
|
||||||
|
"loanID": fulfillment_id,
|
||||||
|
"operatorURL": operatorURL,
|
||||||
|
"validUntil": dsp_until
|
||||||
|
}
|
||||||
|
|
||||||
|
if forceTestBehaviour:
|
||||||
|
return new_loan_record
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import calibre_plugins.deacsm.prefs as prefs # type: ignore
|
import calibre_plugins.deacsm.prefs as prefs # type: ignore
|
||||||
deacsmprefs = prefs.DeACSM_Prefs()
|
deacsmprefs = prefs.DeACSM_Prefs()
|
||||||
|
@ -552,14 +560,7 @@ def updateLoanReturnData(fulfillmentResultToken):
|
||||||
# books, and can then return them.
|
# books, and can then return them.
|
||||||
# Also, the config widget is responsible for cleaning up that list.
|
# Also, the config widget is responsible for cleaning up that list.
|
||||||
|
|
||||||
deacsmprefs["list_of_rented_books"].append({
|
deacsmprefs["list_of_rented_books"].append(new_loan_record)
|
||||||
"book_name": book_name,
|
|
||||||
"user": userUUID,
|
|
||||||
"device": deviceUUID,
|
|
||||||
"loanID": loanID,
|
|
||||||
"operatorURL": operatorURL,
|
|
||||||
"validUntil": dsp_until
|
|
||||||
})
|
|
||||||
|
|
||||||
deacsmprefs.writeprefs()
|
deacsmprefs.writeprefs()
|
||||||
|
|
||||||
|
@ -762,7 +763,9 @@ def performFulfillmentNotification(fulfillmentResultToken, forceOptional = False
|
||||||
doc_send = "<?xml version=\"1.0\"?>\n" + etree.tostring(full_text_xml, encoding="utf-8", pretty_print=True, xml_declaration=False).decode("utf-8")
|
doc_send = "<?xml version=\"1.0\"?>\n" + etree.tostring(full_text_xml, encoding="utf-8", pretty_print=True, xml_declaration=False).decode("utf-8")
|
||||||
|
|
||||||
# Debug: Print notify request
|
# Debug: Print notify request
|
||||||
#print(doc_send)
|
if (verbose_logging):
|
||||||
|
print("Notify payload XML:")
|
||||||
|
print(doc_send)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
code, msg = sendRequestDocuRC(doc_send, url)
|
code, msg = sendRequestDocuRC(doc_send, url)
|
||||||
|
|
110
tests/main.py
110
tests/main.py
|
@ -172,6 +172,37 @@ class TestAdobe(unittest.TestCase):
|
||||||
self.assertEqual(sha_hash, "3452e3d11cdd70eb90323f291c06afafe10e098a", "Invalid SHA hash for node signing")
|
self.assertEqual(sha_hash, "3452e3d11cdd70eb90323f291c06afafe10e098a", "Invalid SHA hash for node signing")
|
||||||
|
|
||||||
|
|
||||||
|
def test_hash_node_returnbugfix(self):
|
||||||
|
'''Check if the XML hash is correct when returning a book ...'''
|
||||||
|
|
||||||
|
# I don't think there's ever a case where the hashing algorithm is different,
|
||||||
|
# but I needed this test during debugging and thought, hey, why not leave it in.
|
||||||
|
|
||||||
|
mock_xml_str = """
|
||||||
|
<adept:notification xmlns:adept="http://ns.adobe.com/adept">
|
||||||
|
<adept:user>urn:uuid:6e5393e0-ff13-4ae8-8f6c-6654182ac7d5</adept:user>
|
||||||
|
<adept:device>urn:uuid:51abfbaf-f0e8-474d-b031-626c5224f90f</adept:device>
|
||||||
|
<adept:nonce>eVr2pi26AAAAAAAA</adept:nonce>
|
||||||
|
<adept:expiration>2022-08-03T09:16:22Z</adept:expiration>
|
||||||
|
<body xmlns="http://ns.adobe.com/adept">
|
||||||
|
<fulfillment>6ccfbc7a-349b-40ad-82d8-d7a4c717ca13-00000271</fulfillment>
|
||||||
|
<transaction>237493726-1749302749327354-Wed Aug 03 09:16:22 UTC 2022</transaction>
|
||||||
|
<user>urn:uuid:6e5393e0-ff13-4ae8-8f6c-6654182ac7d5</user>
|
||||||
|
<fulfilled>true</fulfilled>
|
||||||
|
<returned>true</returned>
|
||||||
|
<hmac>CB3Ql1FAJD957t5n749q5ZO8IzU=</hmac>
|
||||||
|
</body>
|
||||||
|
</adept:notification>
|
||||||
|
"""
|
||||||
|
|
||||||
|
mock_xml_obj = etree.fromstring(mock_xml_str)
|
||||||
|
sha_hash = libadobe.hash_node(mock_xml_obj).hexdigest().lower()
|
||||||
|
|
||||||
|
self.assertEqual(sha_hash, "8b0a24ba37c4333d93650c6ce52f8ee779f21533", "Invalid SHA hash for node signing")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def test_sign_node_old(self):
|
def test_sign_node_old(self):
|
||||||
|
|
||||||
'''Check if the external RSA library (unused) signs correctly'''
|
'''Check if the external RSA library (unused) signs correctly'''
|
||||||
|
@ -324,6 +355,85 @@ class TestAdobe(unittest.TestCase):
|
||||||
self.assertEqual(binascii.hexlify(msg), binascii.hexlify(expected_msg), "devkey encryption returned invalid result")
|
self.assertEqual(binascii.hexlify(msg), binascii.hexlify(expected_msg), "devkey encryption returned invalid result")
|
||||||
|
|
||||||
|
|
||||||
|
class TestPluginInterface(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def forcefail(self):
|
||||||
|
self.assertEqual(1, 2, "force fail")
|
||||||
|
|
||||||
|
def test_loanReturnFulfillmentID(self):
|
||||||
|
'''Check if proper ID is used for the loan token'''
|
||||||
|
|
||||||
|
# Previous versions of the plugin had a bug where sometimes the wrong loan token
|
||||||
|
# was used, which caused wrong (or no) books to be returned to a library.
|
||||||
|
# Adding a test case so this never happens again ...
|
||||||
|
|
||||||
|
|
||||||
|
mock_data = """
|
||||||
|
|
||||||
|
<envelope xmlns="http://ns.adobe.com/adept">
|
||||||
|
<fulfillmentResult>
|
||||||
|
<fulfillment>34659b20-92c8-4004-9fd8-c5174e7eed47-00010214</fulfillment>
|
||||||
|
<returnable>true</returnable>
|
||||||
|
<initial>false</initial>
|
||||||
|
<resourceItemInfo>
|
||||||
|
<resource>urn:uuid:b7c6ccb8-1012-44a9-9c8b-0388d0c685f7</resource>
|
||||||
|
<resourceItem>0</resourceItem>
|
||||||
|
<metadata>
|
||||||
|
<dc:title xmlns:dc="http://purl.org/dc/elements/1.1/">Book title for test</dc:title>
|
||||||
|
</metadata>
|
||||||
|
<licenseToken>
|
||||||
|
<user>urn:uuid:2bd57a81-6192-4a1b-8eb2-64e2d197f9fa</user>
|
||||||
|
<resource>urn:uuid:b7c6ccb8-1012-44a9-9c8b-0388d0c685f7</resource>
|
||||||
|
<deviceType>standalone</deviceType>
|
||||||
|
<device>urn:uuid:83681cbb-b6df-44a3-a423-c2b37ba66e84</device>
|
||||||
|
<operatorURL>https://acs.example.com/fulfillment</operatorURL>
|
||||||
|
<fulfillment>34659b20-92c8-4004-9fd8-c5174e7eed47-00010214</fulfillment>
|
||||||
|
<distributor>urn:uuid:1f5c2437-58f2-4a24-9495-3e99155e6f98</distributor>
|
||||||
|
<permissions>
|
||||||
|
<display>
|
||||||
|
<loan>34659b20-92c8-4004-9fd8-c5174e7eed47-00010214</loan>
|
||||||
|
<until>2022-07-03T01:14:42Z</until>
|
||||||
|
</display>
|
||||||
|
</permissions>
|
||||||
|
</licenseToken>
|
||||||
|
</resourceItemInfo>
|
||||||
|
</fulfillmentResult>
|
||||||
|
|
||||||
|
<loanToken>
|
||||||
|
<time>2022-07-01T03:17:22+00:00</time>
|
||||||
|
<user>urn:uuid:2bd57a81-6192-4a1b-8eb2-64e2d197f9fa</user>
|
||||||
|
<operatorURL>https://acs.example.com/fulfillment</operatorURL>
|
||||||
|
<licenseURL>https://nasigningservice.adobe.com/licensesign</licenseURL>
|
||||||
|
<loan>6d2dc249-2bc0-43e1-a130-3866f85020d9-00003487</loan>
|
||||||
|
<loan>6d2dc249-2bc0-43e1-a130-3866f85020d9-00003467</loan>
|
||||||
|
<loan>34659b20-92c8-4004-9fd8-c5174e7eed47-00010214</loan>
|
||||||
|
<loan>11197eb9-3543-4b41-9c6e-03ffeaf277c0-00024754</loan>
|
||||||
|
</loanToken>
|
||||||
|
</envelope>
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
extracted_token = libadobeFulfill.updateLoanReturnData(etree.fromstring(mock_data), forceTestBehaviour=True)
|
||||||
|
|
||||||
|
expected_token = {
|
||||||
|
"book_name": "Book title for test",
|
||||||
|
"device": 'urn:uuid:83681cbb-b6df-44a3-a423-c2b37ba66e84',
|
||||||
|
"user": 'urn:uuid:2bd57a81-6192-4a1b-8eb2-64e2d197f9fa',
|
||||||
|
"operatorURL": 'https://acs.example.com/fulfillment',
|
||||||
|
"loanID": '34659b20-92c8-4004-9fd8-c5174e7eed47-00010214',
|
||||||
|
"validUntil": '2022-07-03T01:14:42Z'
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assertEqual(extracted_token, expected_token, "Loan record generator broken")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class TestOther(unittest.TestCase):
|
class TestOther(unittest.TestCase):
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user