def getMetadata(identifier, user=ezidapp.models.AnonymousUser, prefixMatch=False): """ Returns all metadata for a given qualified identifier, e.g., "doi:10.5060/FOO". 'user' is the requestor and should be an authenticated StoreUser object. The successful return is a pair (status, dictionary) where 'status' is a string that includes the canonical, qualified form of the identifier, as in: success: doi:10.5060/FOO and 'dictionary' contains element (name, value) pairs. Unsuccessful returns include the strings: error: forbidden error: bad request - subreason... error: internal server error error: concurrency limit exceeded If 'prefixMatch' is true, prefix matching is enabled and the returned identifier is the longest identifier that matches a (possibly proper) prefix of the requested identifier. In such a case, the status string resembles: success: doi:10.5060/FOO in_lieu_of doi:10.5060/FOOBAR """ nqidentifier = util.normalizeIdentifier(identifier) if nqidentifier == None: return "error: bad request - invalid identifier" tid = uuid.uuid1() if not _acquireIdentifierLock(nqidentifier, user.username): return "error: concurrency limit exceeded" try: log.begin(tid, "getMetadata", nqidentifier, user.username, user.pid, user.group.groupname, user.group.pid, str(prefixMatch)) si = ezidapp.models.getIdentifier(nqidentifier, prefixMatch) if not policy.authorizeView(user, si): log.forbidden(tid) return "error: forbidden" d = si.toLegacy() util2.convertLegacyToExternal(d) if si.isDoi: d["_shadowedby"] = si.arkAlias log.success(tid) if prefixMatch and si.identifier != nqidentifier: return ("success: %s in_lieu_of %s" % (si.identifier, nqidentifier), d) else: return ("success: " + nqidentifier, d) except ezidapp.models.StoreIdentifier.DoesNotExist: log.badRequest(tid) return "error: bad request - no such identifier" except Exception, e: log.error(tid, e) return "error: internal server error"
def mintIdentifier(shoulder, user, metadata={}): """ Mints an identifier under the given qualified shoulder, e.g., "doi:10.5060/". 'user' is the requestor and should be an authenticated StoreUser object. 'metadata' should be a dictionary of element (name, value) pairs. If an initial target URL is not supplied, the identifier is given a self-referential target URL. The successful return is a string that includes the canonical, qualified form of the new identifier, as in: success: ark:/95060/fk35717n0h For DOI identifiers, the string also includes the qualified shadow ARK, as in: success: doi:10.5060/FK35717N0H | ark:/b5060/fk35717n0h Unsuccessful returns include the strings: error: forbidden error: bad request - subreason... error: internal server error error: concurrency limit exceeded """ tid = uuid.uuid1() try: log.begin(tid, "mintIdentifier", shoulder, user.username, user.pid, user.group.groupname, user.group.pid) s = ezidapp.models.getExactShoulderMatch(shoulder) if s == None: log.badRequest(tid) return "error: bad request - no such shoulder" if s.isUuid: identifier = "uuid:" + str(uuid.uuid1()) else: if s.minter == "": log.badRequest(tid) return "error: bad request - shoulder does not support minting" # Minters always return unqualified ARKs. ark = noid_nog.getMinter(s.minter).mintIdentifier() if s.isArk: identifier = "ark:/" + ark elif s.isDoi: doi = util.shadow2doi(ark) assert util.doi2shadow(doi) == ark, "invalid DOI shadow ARK" identifier = "doi:" + doi else: assert False, "unhandled case" assert identifier.startswith(s.prefix),\ "minted identifier does not match shoulder" except Exception, e: log.error(tid, e) return "error: internal server error"
def deleteIdentifier(identifier, user, updateExternalServices=True): """ Deletes an identifier having the given qualified name, e.g., "doi:10.5060/FOO". 'user' is the requestor and should be an authenticated StoreUser object. The successful return is a string that includes the canonical, qualified form of the now-nonexistent identifier, as in: success: doi:/10.5060/FOO Unsuccessful returns include the strings: error: forbidden error: bad request - subreason... error: internal server error error: concurrency limit exceeded """ nqidentifier = util.normalizeIdentifier(identifier) if nqidentifier == None: return "error: bad request - invalid identifier" tid = uuid.uuid1() if not _acquireIdentifierLock(nqidentifier, user.username): return "error: concurrency limit exceeded" try: log.begin( tid, "deleteIdentifier", nqidentifier, user.username, user.pid, user.group.groupname, user.group.pid, ) si = ezidapp.models.getIdentifier(nqidentifier) if not policy.authorizeDelete(user, si): log.forbidden(tid) return "error: forbidden" if not si.isReserved and not user.isSuperuser: log.badRequest(tid) return "error: bad request - identifier status does not support deletion" with django.db.transaction.atomic(): si.delete() ezidapp.models.update_queue.enqueue(si, "delete", updateExternalServices) except ezidapp.models.StoreIdentifier.DoesNotExist: log.badRequest(tid) return "error: bad request - no such identifier" except Exception, e: log.error(tid, e) return "error: internal server error"
def setMetadata(identifier, user, metadata, updateExternalServices=True, internalCall=False): """ Sets metadata elements of a given qualified identifier, e.g., "doi:10.5060/FOO". 'user' is the requestor and should be an authenticated StoreUser object. 'metadata' should be a dictionary of element (name, value) pairs. If an element being set already exists, it is overwritten, if not, it is created; existing elements not set are left unchanged. Of the reserved metadata elements, only "_owner", "_target", "_profile", "_status", and "_export" may be set (unless the user is the EZID administrator). The "_crossref" element may be set only in certain situations. The successful return is a string that includes the canonical, qualified form of the identifier, as in: success: doi:10.5060/FOO Unsuccessful returns include the strings: error: forbidden error: bad request - subreason... error: internal server error error: concurrency limit exceeded """ nqidentifier = util.normalizeIdentifier(identifier) if nqidentifier == None: return "error: bad request - invalid identifier" tid = uuid.uuid1() if not internalCall: if not _acquireIdentifierLock(nqidentifier, user.username): return "error: concurrency limit exceeded" try: log.begin(tid, "setMetadata", nqidentifier, user.username, user.pid, user.group.groupname, user.group.pid, *[a for p in metadata.items() for a in p]) si = ezidapp.models.getIdentifier(nqidentifier) if not policy.authorizeUpdate(user, si): log.forbidden(tid) return "error: forbidden" previousOwner = si.owner si.updateFromUntrustedLegacy(metadata, allowRestrictedSettings=user.isSuperuser) if si.isCrossref and not si.isReserved and updateExternalServices: si.crossrefStatus = ezidapp.models.StoreIdentifier.CR_WORKING si.crossrefMessage = "" if "_updated" not in metadata: si.updateTime = "" si.my_full_clean() if si.owner != previousOwner: if not policy.authorizeOwnershipChange(user, previousOwner, si.owner): log.badRequest(tid) return "error: bad request - ownership change prohibited" with django.db.transaction.atomic(): si.save() ezidapp.models.update_queue.enqueue(si, "update", updateExternalServices) except ezidapp.models.StoreIdentifier.DoesNotExist: log.badRequest(tid) return "error: bad request - no such identifier" except django.core.exceptions.ValidationError, e: log.badRequest(tid) return "error: bad request - " + util.formatValidationError(e)
def createIdentifier(identifier, user, metadata={}, updateIfExists=False): """ Creates an identifier having the given qualified name, e.g., "doi:10.5060/FOO". 'user' is the requestor and should be an authenticated StoreUser object. 'metadata' should be a dictionary of element (name, value) pairs. If an initial target URL is not supplied, the identifier is given a self-referential target URL. The successful return is a string that includes the canonical, qualified form of the new identifier, as in: success: ark:/95060/foo For DOI identifiers, the string also includes the qualified shadow ARK, as in: success: doi:10.5060/FOO | ark:/b5060/foo Unsuccessful returns include the strings: error: forbidden error: bad request - subreason... error: internal server error error: concurrency limit exceeded If 'updateIfExists' is true, an "identifier already exists" error falls through to a 'setMetadata' call. """ nqidentifier = util.normalizeIdentifier(identifier) if nqidentifier == None: return "error: bad request - invalid identifier" tid = uuid.uuid1() if not _acquireIdentifierLock(nqidentifier, user.username): return "error: concurrency limit exceeded" try: log.begin(tid, "createIdentifier", nqidentifier, user.username, user.pid, user.group.groupname, user.group.pid, *[a for p in metadata.items() for a in p]) if not policy.authorizeCreate(user, nqidentifier): log.forbidden(tid) return "error: forbidden" si = ezidapp.models.StoreIdentifier( identifier=nqidentifier, owner=(None if user == ezidapp.models.AnonymousUser else user)) si.updateFromUntrustedLegacy(metadata, allowRestrictedSettings=user.isSuperuser) if si.isDoi: s = ezidapp.models.getLongestShoulderMatch(si.identifier) # Should never happen. assert s != None, "no matching shoulder found" if s.isDatacite: if si.datacenter == None: si.datacenter = s.datacenter elif s.isCrossref: if not si.isCrossref: if si.isReserved: si.crossrefStatus = ezidapp.models.StoreIdentifier.CR_RESERVED else: si.crossrefStatus = ezidapp.models.StoreIdentifier.CR_WORKING else: assert False, "unhandled case" si.my_full_clean() if si.owner != user: if not policy.authorizeOwnershipChange(user, user, si.owner): log.badRequest(tid) return "error: bad request - ownership change prohibited" with django.db.transaction.atomic(): si.save() ezidapp.models.update_queue.enqueue(si, "create") except django.core.exceptions.ValidationError, e: log.badRequest(tid) return "error: bad request - " + util.formatValidationError(e)
si.crossrefStatus = ezidapp.models.StoreIdentifier.CR_WORKING else: assert False, "unhandled case" si.my_full_clean() if si.owner != user: if not policy.authorizeOwnershipChange(user, user, si.owner): log.badRequest(tid) return "error: bad request - ownership change prohibited" with django.db.transaction.atomic(): si.save() ezidapp.models.update_queue.enqueue(si, "create") except django.core.exceptions.ValidationError, e: log.badRequest(tid) return "error: bad request - " + util.formatValidationError(e) except django.db.utils.IntegrityError: log.badRequest(tid) if updateIfExists: return setMetadata(identifier, user, metadata, internalCall=True) else: return "error: bad request - identifier already exists" except Exception, e: log.error(tid, e) return "error: internal server error" else: log.success(tid) if si.isDoi: return "success: %s | %s" % (nqidentifier, si.arkAlias) else: return "success: " + nqidentifier finally: _releaseIdentifierLock(nqidentifier, user.username)
def _mintIdentifier(shoulder, user, metadata={}): """ Mints an identifier under the given qualified shoulder, e.g., "doi:10.5060/". 'user' is the requestor and should be an authenticated StoreUser object. 'metadata' should be a dictionary of element (name, value) pairs. If an initial target URL is not supplied, the identifier is given a self-referential target URL. The successful return is a string that includes the canonical, qualified form of the new identifier, as in: success: ark:/95060/fk35717n0h For DOI identifiers, the string also includes the qualified shadow ARK, as in: success: doi:10.5060/FK35717N0H | ark:/b5060/fk35717n0h Unsuccessful returns include the strings: error: forbidden error: bad request - subreason... error: internal server error error: concurrency limit exceeded """ tid = uuid.uuid1() # TODO: We want to be able to support rendering error messages to end users in # production like current version of EZID does without breaking rendering of # Django's exception diagnostics page in debug mode and without # having to wrap large sections of code in exception handlers just for redirecting # to a logger. log.begin( tid, "mintIdentifier", shoulder, user.username, user.pid, user.group.groupname, user.group.pid, ) shoulder_model = ezidapp.models.getExactShoulderMatch(shoulder) if shoulder_model is None: log.badRequest(tid) # TODO: Errors should be raised, not returned. return "error: bad request - no such shoulder" if shoulder_model.isUuid: identifier = "uuid:" + str(uuid.uuid1()) else: if shoulder_model.minter == "": log.badRequest(tid) return "error: bad request - shoulder does not support minting" identifier = minter.mint_id(shoulder_model) logger.debug('Minter returned identifier: {}'.format(identifier)) if shoulder_model.prefix.startswith('doi:'): identifier = shoulder_model.prefix + identifier.upper() elif shoulder_model.prefix.startswith('ark:/'): identifier = shoulder_model.prefix + identifier.lower() else: raise False, 'Expected ARK or DOI prefix, not "{}"'.format( shoulder_model.prefix) logger.debug('Final shoulder + identifier: {}'.format(identifier)) log.success(tid, identifier) return createIdentifier(identifier, user, metadata)