Compare commits

...
8 Commits
7 changed files with 100 additions and 159 deletions
@@ -10,7 +10,7 @@ import {
} from "semantic-ui-react"; } from "semantic-ui-react";
import { FieldArray } from "formik"; import { FieldArray } from "formik";
import { DoiRest, MapDatacite } from "datacite-rest"; import { FetchDoi, MapDatacite } from "datacite-rest";
export class DoiMint extends Component { export class DoiMint extends Component {
constructor(props) { constructor(props) {
@@ -47,65 +47,66 @@ export class DoiMint extends Component {
// this should fetch a new doi // this should fetch a new doi
var pushDoi = (form) => { var pushDoi = (form) => {
// activate the loader // activate the loader
this.setState({ this.setState({
showLoader: true, showLoader: true,
}); });
const url = this.configs.datacite_url; // get the prefix from backend
const auth = {
username: this.configs.datacite_uname,
password: this.configs.datacite_pass,
};
const prefix = this.configs.datacite_prefix; const prefix = this.configs.datacite_prefix;
const suffix = this.configs.datacite_suffix;
// get mapped DOI if (prefix !== null && suffix !== null) {
const mapped = MapDatacite(this.metadata, this.record.id, prefix); // get mapped DOI
const mapped = MapDatacite(this.metadata, this.record.id, prefix, suffix);
const _doirest = new DoiRest(url); const _fetchdoi = new FetchDoi("/getdoi");
// Create a new DOI // Create a new DOI
_doirest _fetchdoi
.create(mapped, auth, this.configs.datacite_password_iv) .create(mapped)
.then((data) => { .then((data) => {
// if there is an error // if there is an error
if (data.data.errors) { if (data.data.errors) {
this.setState({ this.setState({
showLoader: false, showLoader: false,
isError: true, isError: true,
errorMsg: data.data.errors[0].title, errorMsg: data.data.errors[0].title,
}); });
// if credentials are wrong! }
} else if (data.code == 405) { // new doi is fetched
this.setState({ else {
showLoader: false, // add new identifier
isError: true, const _identifiers = [
errorMsg: "Not configured!", {
}); identifier: data.data.data.data.id,
} scheme: "doi",
// new doi is fetched },
else { ];
// add new identifier // submit the value to the form
const _identifiers = [ this.setState({ identifiers: _identifiers });
{ form.setFieldValue(
identifier: data.data.data.id, "metadata.identifiers",
scheme: "doi", this.state.identifiers
}, );
];
// submit the value to the form
this.setState({ identifiers: _identifiers });
form.setFieldValue("metadata.identifiers", this.state.identifiers);
this.is_doi = true; this.is_doi = true;
// deactivate the loader // deactivate the loader
this.setState({ this.setState({
showLoader: false, showLoader: false,
doi_id: this.state.identifiers[0].identifier, doi_id: this.state.identifiers[0].identifier,
}); });
} }
}) })
.catch((error) => { .catch((error) => {
console.log("error", error); console.log("error", error);
});
} else {
this.setState({
showLoader: false,
isError: true,
errorMsg: "Not configured!",
}); });
}
}; };
// get a link of dio // get a link of dio
@@ -175,6 +176,12 @@ export class DoiMint extends Component {
marginTop: "10px", marginTop: "10px",
}} }}
> >
{this.state.doi_id && (
<Message positive>
<Message.Header>A DOI is registered!</Message.Header>
<p>Please save the deposit. </p>
</Message>
)}
<Label <Label
style={{ style={{
cursor: "pointer", cursor: "pointer",
+2 -1
View File
@@ -110,7 +110,8 @@ TUG_ROUTES = {
"comingsoon": "/comingsoon", "comingsoon": "/comingsoon",
"deposit_create": "/uploads/new", "deposit_create": "/uploads/new",
"deposit_edit": "/uploads/<pid_value>", "deposit_edit": "/uploads/<pid_value>",
"record_detail": "/records/<pid_value>" "getdoi": "/getdoi",
"record_detail": "/records/<pid_value>",
} }
# Datacite # Datacite
-88
View File
@@ -1,88 +0,0 @@
#!/usr/bin/env python
"""crypto helper module. see https://gist.github.com/marcoslin/8026990."""
import binascii
from Crypto import Random
from Crypto.Cipher import AES
# ------------------------------
# DEFINE Encryption Class
class Cryptor(object):
"""Crypto class implementation.
Provide encryption and decryption function that works with crypto-js.
https://code.google.com/p/crypto-js/
Padding implemented as per RFC 2315: PKCS#7 page 21
http://www.ietf.org/rfc/rfc2315.txt
The key to make pycrypto work with crypto-js are:
1. Use MODE_CFB. For some reason, crypto-js decrypted result from MODE_CBC
gets truncated
2. Use Pkcs7 padding as per RFC 2315, the default padding used by CryptoJS
3. On the JS side, make sure to wrap ciphertext with CryptoJS.lib.CipherParams.create()
"""
# AES-256 key (32 bytes)
KEY = "01ab38d5e05c92aa098921d9d4626107133c7e2ab0e4849558921ebcc242bcb0"
BLOCK_SIZE = 16
@classmethod
def _pad_string(cls, in_string):
"""Pad an input string according to PKCS#7."""
in_len = len(in_string)
pad_size = cls.BLOCK_SIZE - (in_len % cls.BLOCK_SIZE)
return in_string.ljust(in_len + pad_size, chr(pad_size))
@classmethod
def _unpad_string(cls, in_string):
"""Remove the PKCS#7 padding from a text string."""
in_len = len(in_string)
pad_size = ord(in_string[-1])
if pad_size > cls.BLOCK_SIZE:
raise ValueError("Input is not padded or padding is corrupt")
return in_string[: in_len - pad_size]
@classmethod
def generate_iv(cls, size=16):
"""Generate initialization vector."""
return Random.get_random_bytes(size)
@classmethod
def encrypt(cls, in_string, in_key, in_iv=None):
"""Return encrypted string.
@in_string: Simple str to be encrypted
@key: hexified key
@iv: hexified iv
"""
key = binascii.a2b_hex(in_key)
if in_iv is None:
iv = cls.generate_iv()
in_iv = binascii.b2a_hex(iv)
else:
iv = binascii.a2b_hex(in_iv)
aes = AES.new(key, AES.MODE_CFB, iv, segment_size=128)
padded = cls._pad_string(in_string).encode("utf-8")
encrypted = aes.encrypt(padded)
return in_iv, encrypted
@classmethod
def decrypt(cls, in_encrypted, in_key, in_iv):
"""Return encrypted string.
@in_encrypted: Base64 encoded
@key: hexified key
@iv: hexified iv
"""
key = binascii.a2b_hex(in_key)
iv = binascii.a2b_hex(in_iv)
aes = AES.new(key, AES.MODE_CFB, iv, segment_size=128)
decrypted = aes.decrypt(binascii.a2b_base64(in_encrypted).rstrip())
return cls._unpad_string(decrypted)
+1 -1
View File
@@ -12,4 +12,4 @@ This file is imported by ``invenio_theme_tugraz.__init__``,
and parsed by ``setup.py``. and parsed by ``setup.py``.
""" """
__version__ = "1.9.1" __version__ = "1.9.2"
+35 -13
View File
@@ -12,8 +12,9 @@ import binascii
from os import environ from os import environ
from typing import Dict from typing import Dict
import requests
from elasticsearch_dsl.utils import AttrDict from elasticsearch_dsl.utils import AttrDict
from flask import Blueprint, current_app, g, render_template from flask import Blueprint, current_app, g, render_template, request
from flask_login import login_required from flask_login import login_required
from flask_menu import current_menu from flask_menu import current_menu
from invenio_app_rdm.records_ui.views.decorators import ( from invenio_app_rdm.records_ui.views.decorators import (
@@ -32,7 +33,6 @@ from invenio_rdm_records.resources.config import RDMDraftFilesResourceConfig
from invenio_rdm_records.resources.serializers import UIJSONSerializer from invenio_rdm_records.resources.serializers import UIJSONSerializer
from invenio_rdm_records.services import RDMDraftFilesService from invenio_rdm_records.services import RDMDraftFilesService
from .crypto import Cryptor
from .search import FrontpageRecordsSearch from .search import FrontpageRecordsSearch
@@ -51,6 +51,7 @@ def ui_blueprint(app):
blueprint.add_url_rule(routes["comingsoon"], view_func=comingsoon) blueprint.add_url_rule(routes["comingsoon"], view_func=comingsoon)
blueprint.add_url_rule(routes["deposit_create"], view_func=deposit_create) blueprint.add_url_rule(routes["deposit_create"], view_func=deposit_create)
blueprint.add_url_rule(routes["record_detail"], view_func=record_detail) blueprint.add_url_rule(routes["record_detail"], view_func=record_detail)
blueprint.add_url_rule(routes["getdoi"], view_func=retrieve_doi, methods=["POST"])
@blueprint.app_template_filter("make_dict_like") @blueprint.app_template_filter("make_dict_like")
def make_dict_like(value: str, key: str) -> Dict[str, str]: def make_dict_like(value: str, key: str) -> Dict[str, str]:
@@ -83,22 +84,42 @@ def comingsoon():
def get_datacite_details(): def get_datacite_details():
"""Application credentials for DOI.""" """Application credentials for DOI."""
url = environ.get('INVENIO_DATACITE_URL') or "" prefix = environ.get("INVENIO_DATACITE_PREFIX")
username = environ.get('INVENIO_DATACITE_UNAME') or "" suffix = environ.get("INVENIO_DATACITE_SUFFIX")
password = environ.get('INVENIO_DATACITE_PASS') or ""
prefix = environ.get('INVENIO_DATACITE_PREFIX') or ""
password_iv, encrypted_password = Cryptor.encrypt(password, Cryptor.KEY)
details = { details = {
"datacite_url": url,
"datacite_uname": username,
"datacite_pass": binascii.b2a_base64(encrypted_password).rstrip(),
"datacite_prefix": prefix, "datacite_prefix": prefix,
"datacite_password_iv": password_iv, "datacite_suffix": suffix,
} }
return details return details
@login_required
def retrieve_doi():
"""Retrieve DOI from datacite API."""
doi_metadata = request.get_json()
url = environ.get("INVENIO_DATACITE_URL")
username = environ.get("INVENIO_DATACITE_UNAME")
password = environ.get("INVENIO_DATACITE_PASS")
doi_response = requests.post(
url,
auth=(username, password),
json=doi_metadata,
)
response_data = {"code": doi_response.status_code}
try:
doi_response.raise_for_status()
response_data["data"] = doi_response.json()
except requests.exceptions.RequestException:
response_data["errors"] = doi_response.json()["errors"]
return response_data, response_data["code"]
# #
# TODO: change this override behaviour once # TODO: change this override behaviour once
# PR is merged: # PR is merged:
@@ -138,6 +159,7 @@ def deposit_edit(draft=None, pid_value=None):
# TODO: get the `is_published` field when reading the draft # TODO: get the `is_published` field when reading the draft
from invenio_pidstore.errors import PIDUnregistered from invenio_pidstore.errors import PIDUnregistered
try: try:
service().draft_cls.pid.resolve(pid_value, registered_only=True) service().draft_cls.pid.resolve(pid_value, registered_only=True)
record["is_published"] = True record["is_published"] = True
@@ -158,7 +180,7 @@ def deposit_edit(draft=None, pid_value=None):
@pass_record @pass_record
@pass_record_files @pass_record_files
@user_permissions(actions=['update_draft']) @user_permissions(actions=["update_draft"])
def record_detail(record=None, files=None, pid_value=None, permissions=None): def record_detail(record=None, files=None, pid_value=None, permissions=None):
"""Record detail page (aka landing page).""" """Record detail page (aka landing page)."""
files_dict = None if files is None else files.to_dict() files_dict = None if files is None else files.to_dict()
+1 -1
View File
@@ -40,7 +40,7 @@ theme = WebpackThemeBundle(
'@ckeditor/ckeditor5-react': '^2.1.0', '@ckeditor/ckeditor5-react': '^2.1.0',
# datacite - rest api plugin # datacite - rest api plugin
'datacite-rest': '^0.1.4', 'datacite-rest': '^0.1.7',
}, },
) )
+1 -2
View File
@@ -58,10 +58,9 @@ install_requires = [
"invenio-i18n>=1.2.0", "invenio-i18n>=1.2.0",
"elasticsearch_dsl>=7.2.1", "elasticsearch_dsl>=7.2.1",
"invenio_search>=1.4.0,<2.0.0", "invenio_search>=1.4.0,<2.0.0",
"invenio_config_tugraz>=0.5.1",
# keep this package updated. # keep this package updated.
"invenio_app_rdm<=1.0.0", "invenio_app_rdm<=1.0.0",
# needed for DOI credential encryption
"pycryptodome==3.10.1",
] ]
packages = find_packages() packages = find_packages()