Repository: hydroshare/hydroshare

Issue:
Add validation for ORCID identifiers so that they match Crossref schema
**Description of the bug**
If a user editing their profile submits an ORCID identifier that is a url, it will be deemed valid even if it does not specifically match the form for an ORCID link. We should strengthen our validation around ORCID (and possibly other) identifiers:
https://github.com/hydroshare/hydroshare/blob/2.12.3/hs_core/models.py#L677-L725

This is particularly important because we include identifiers in our XML submission to Crossref. If the identifier doesn't match their schema, the DOI request will be rejected.

Currently this resource is pending because of this issue:
https://www.hydroshare.org/resource/db248589a52a41c4a6f387bbefbab3a9/

Message from Crossref deposit 1611126012
```
<?xml version="1.0" encoding="UTF-8"?>
<doi_batch_diagnostic status="completed" sp="ds5">
   <submission_id>1611126012</submission_id>
   <batch_id>db248589a52a41c4a6f387bbefbab3a9</batch_id>
   <record_diagnostic status="Failure">
      <doi />
      <msg>Error: cvc-pattern-valid: Value 'https://orcid.org/my-orcid?orcid=0000-0002-5995-9297' is not facet-valid with respect to pattern 'https?://orcid.org/[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{3}[X0-9]{1}' for type 'orcid_t'.
Error: cvc-complex-type.2.2: Element 'ORCID' must have no element [children], and the value must be valid.
</msg>
   </record_diagnostic>
   <batch_data>
      <record_count>1</record_count>
      <success_count>0</success_count>
      <warning_count>0</warning_count>
      <failure_count>1</failure_count>
   </batch_data>
</doi_batch_diagnostic>
```


**Expected behavior**
We should ensure that our identifiers are validated at entry. We should also update existing identifiers so that they are conformant.

**Additional information**
HS v 2.12.3



Discussion:
```
>>> from hs_core.models import BaseResource
>>> res = BaseResource.objects.get(short_id='db248589a52a41c4a6f387bbefbab3a9')
>>> res.metadata.creators.all()
<QuerySet [<Creator: Sharma, Abhinav>, <Creator: Castro-Bolinaga, Celso>]>
>>> u = res.metadata.creators.first()
<Creator: Sharma, Abhinav>
>>> u.identifiers
{'ORCID': 'https://orcid.org/my-orcid?orcid=0000-0002-5995-9297'}
```

Resolving patch:
diff --git a/hs_core/models.py b/hs_core/models.py
--- a/hs_core/models.py
+++ b/hs_core/models.py
@@ -513,11 +513,15 @@ class Party(AbstractMetaDataElement):
     # each identifier is stored as a key/value pair {name:link}
     identifiers = HStoreField(default=dict)
 
-    # list of identifier currently supported
-    supported_identifiers = {'ResearchGateID': 'https://www.researchgate.net/',
-                             'ORCID': 'https://orcid.org/',
-                             'GoogleScholarID': 'https://scholar.google.com/',
-                             'ResearcherID': 'https://www.researcherid.com/'}
+    # list of identifiers currently supported
+    supported_identifiers = {'ResearchGateID':
+                             re.compile(r'^https:\/\/www\.researchgate\.net\/profile\/[^\s]+$'),
+                             'ORCID':
+                             re.compile(r'^https:\/\/orcid\.org\/[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{4}$'),
+                             'GoogleScholarID':
+                             re.compile(r'^https:\/\/scholar\.google\.com\/citations\?.*user=[^\s]+$'),
+                             'ResearcherID':
+                             'https://www.researcherid.com/'}
 
     def __unicode__(self):
         """Return name field for unicode representation."""
@@ -761,9 +765,9 @@ def validate_identifiers(cls, identifiers):
             for id_name in cls.supported_identifiers:
                 id_link = identifiers.get(id_name, '')
                 if id_link:
-                    if not id_link.startswith(cls.supported_identifiers[id_name]) \
-                            or len(id_link) <= len(cls.supported_identifiers[id_name]):
-                        raise ValidationError("URL for {} is invalid".format(id_name))
+                    regex = cls.supported_identifiers[id_name]
+                    if not re.match(regex, id_link):
+                        raise ValidationError(f"URL for {id_name} is invalid.")
         return identifiers
 
 
diff --git a/pytest/conftest.py b/pytest/conftest.py
--- a/pytest/conftest.py
+++ b/pytest/conftest.py
@@ -56,8 +56,8 @@ def base_sample_resource(username='admin', title=str(uuid.uuid4()), contributor=
     con_address = "11 River Drive, Logan UT-84321, USA"
     con_phone = '435-567-0989'
     con_homepage = 'http://usu.edu/homepage/001'
-    con_identifiers = {'ORCID': 'https://orcid.org/mike_s',
-                       'ResearchGateID': 'https://www.researchgate.net/mike_s'}
+    con_identifiers = {'ORCID': 'https://orcid.org/0000-0003-4621-0559',
+                       'ResearchGateID': 'https://www.researchgate.net/profile/mike_s'}
     metadata.append({'contributor': {'name': con_name,
                                      'organization': con_org, 'email': con_email,
                                      'address': con_address, 'phone': con_phone,
@@ -71,8 +71,8 @@ def base_sample_resource(username='admin', title=str(uuid.uuid4()), contributor=
     cr_address = "101 Clarson Ave, Provo UT-84321, USA"
     cr_phone = '801-567=9090'
     cr_homepage = 'http://byu.edu/homepage/002'
-    cr_identifiers = {'ORCID': 'https://orcid.org/john_smith',
-                      'ResearchGateID': 'https://www.researchgate.net/john_smith'}
+    cr_identifiers = {'ORCID': 'https://orcid.org/0000-0003-4621-0559',
+                      'ResearchGateID': 'https://www.researchgate.net/profile/john_smith'}
     metadata.append({'creator': {'name': cr_name, 'organization': cr_org,
                                  'email': cr_email, 'address': cr_address,
                                  'phone': cr_phone, 'homepage': cr_homepage,


Test patch:
diff --git a/hs_core/tests/api/native/test_core_metadata.py b/hs_core/tests/api/native/test_core_metadata.py
--- a/hs_core/tests/api/native/test_core_metadata.py
+++ b/hs_core/tests/api/native/test_core_metadata.py
@@ -581,7 +581,7 @@ def test_party_external_links(self):
         kwargs = {
             "name": "Lisa Howard",
             "email": "lasah@yahoo.com",
-            "identifiers": {"ResearchGateID": "https://www.researchgate.net/LH001"},
+            "identifiers": {"ResearchGateID": "https://www.researchgate.net/profile/LH001"},
         }
         resource.create_metadata_element(self.res.short_id, "creator", **kwargs)
         # test external link (identifiers)
@@ -635,8 +635,8 @@ def test_party_external_links(self):
         # test that multiple identifiers can be created for one creator
         kwargs = {
             "identifiers": {
-                "ResearchGateID": "https://www.researchgate.net/LH001",
-                "ORCID": "https://orcid.org/LH001",
+                "ResearchGateID": "https://www.researchgate.net/profile/LH001",
+                "ORCID": "https://orcid.org/0000-0003-4621-0559",
             }
         }
 
@@ -654,7 +654,7 @@ def test_party_external_links(self):
         for name, link in list(cr_lisa.identifiers.items()):
             self.assertIn(name, ["ResearchGateID", "ORCID"])
             self.assertIn(
-                link, ["https://www.researchgate.net/LH001", "https://orcid.org/LH001"]
+                link, ["https://www.researchgate.net/profile/LH001", "https://orcid.org/0000-0003-4621-0559"]
             )
 
         # test that duplicate identifier name is not allowed - should raise validation error
@@ -672,8 +672,8 @@ def test_party_external_links(self):
         # test that duplicate identifier link is not allowed - should raise validation error
         kwargs = {
             "identifiers": {
-                "researchGate": "https://www.researchgate.net/LH001",
-                "ORCID": "https://www.researchgate.net/LH001",
+                "researchGate": "https://www.researchgate.net/profile/LH001",
+                "ORCID": "https://www.researchgate.net/profile/LH001",
             }
         }
         with self.assertRaises(ValidationError):
@@ -698,7 +698,7 @@ def test_party_external_links(self):
         kwargs = {
             "name": "Lisa Howard",
             "email": "lasah@yahoo.com",
-            "identifiers": {"ResearchGateID": "https://www.researchgate.net/LH001"},
+            "identifiers": {"ResearchGateID": "https://www.researchgate.net/profile/LH001"},
         }
         resource.create_metadata_element(self.res.short_id, "contributor", **kwargs)
         # test external link
@@ -716,9 +716,9 @@ def test_party_external_links(self):
         # test that multiple identifiers can be created for one contributor
         kwargs = {
             "identifiers": {
-                "ResearchGateID": "https://www.researchgate.net/LH001",
-                "ORCID": "https://orcid.org/LH001",
-                "GoogleScholarID": "https://scholar.google.com/LH001",
+                "ResearchGateID": "https://www.researchgate.net/profile/LH001",
+                "ORCID": "https://orcid.org/0000-0003-4621-0559",
+                "GoogleScholarID": "https://scholar.google.com/citations?user=IqoYwgIAAAAJ&hl=en",
                 "ResearcherID": "https://www.researcherid.com/LH001",
             }
         }
@@ -741,9 +741,9 @@ def test_party_external_links(self):
             self.assertIn(
                 link,
                 [
-                    "https://www.researchgate.net/LH001",
-                    "https://orcid.org/LH001",
-                    "https://scholar.google.com/LH001",
+                    "https://www.researchgate.net/profile/LH001",
+                    "https://orcid.org/0000-0003-4621-0559",
+                    "https://scholar.google.com/citations?user=IqoYwgIAAAAJ&hl=en",
                     "https://www.researcherid.com/LH001",
                 ],
             )
diff --git a/hs_core/tests/api/native/test_create_resource.py b/hs_core/tests/api/native/test_create_resource.py
--- a/hs_core/tests/api/native/test_create_resource.py
+++ b/hs_core/tests/api/native/test_create_resource.py
@@ -147,8 +147,8 @@ def test_create_resource_with_metadata(self):
             {'creator': {'name': 'Lisa Molley', 'email': 'lmolley@gmail.com'}},
             {'contributor': {'name': 'Kelvin Marshal', 'email': 'kmarshal@yahoo.com',
                              'organization': 'Utah State University',
-                             'identifiers': {'ORCID': 'https://orcid.org/john',
-                                             'ResearchGateID': 'https://www.researchgate.net/john'}}},
+                             'identifiers': {'ORCID': 'https://orcid.org/0000-0003-4621-0559',
+                                             'ResearchGateID': 'https://www.researchgate.net/profile/john'}}},
             {'coverage': {'type': 'period', 'value': {'name': 'Name for period coverage', 'start': '1/1/2000',
                                                       'end': '12/12/2012'}}},
             {'coverage': {'type': 'point', 'value': {'name': 'Name for point coverage', 'east': '56.45678',
diff --git a/hs_core/tests/api/native/test_update_metadata.py b/hs_core/tests/api/native/test_update_metadata.py
--- a/hs_core/tests/api/native/test_update_metadata.py
+++ b/hs_core/tests/api/native/test_update_metadata.py
@@ -37,8 +37,8 @@ def test_update_science_metadata(self):
             {'creator': {'name': 'Lisa Molley', 'email': 'lmolley@gmail.com'}},
             {'contributor': {'name': 'Kelvin Marshal', 'email': 'kmarshal@yahoo.com',
                              'organization': 'Utah State University',
-                             'identifiers': {'ORCID': 'https://orcid.org/john',
-                                             'ResearchGateID': 'https://www.researchgate.net/john'}
+                             'identifiers': {'ORCID': 'https://orcid.org/0000-0003-4621-0559',
+                                             'ResearchGateID': 'https://www.researchgate.net/profile/john'}
                              }},
             {'coverage': {'type': 'period', 'value': {'name': 'Name for period coverage', 'start': '1/1/2000',
                                                       'end': '12/12/2012'}}},
@@ -143,7 +143,7 @@ def test_update_science_metadata(self):
         for name, link in list(contributor.identifiers.items()):
             self.assertIn(name, ["ResearchGateID", "ORCID"])
             self.assertIn(
-                link, ["https://orcid.org/john", "https://www.researchgate.net/john"]
+                link, ["https://orcid.org/0000-0003-4621-0559", "https://www.researchgate.net/profile/john"]
             )
 
         # there should be now 2 coverage elements
diff --git a/hs_core/tests/api/rest/test_create_resource.py b/hs_core/tests/api/rest/test_create_resource.py
--- a/hs_core/tests/api/rest/test_create_resource.py
+++ b/hs_core/tests/api/rest/test_create_resource.py
@@ -130,8 +130,8 @@ def test_resource_create_with_core_metadata(self):
         con_address = "11 River Drive, Logan UT-84321, USA"
         con_phone = '435-567-0989'
         con_homepage = 'http://usu.edu/homepage/001'
-        con_identifiers = {'ORCID': 'https://orcid.org/mike_s',
-                           'ResearchGateID': 'https://www.researchgate.net/mike_s'}
+        con_identifiers = {'ORCID': 'https://orcid.org/0000-0003-4621-0559',
+                           'ResearchGateID': 'https://www.researchgate.net/profile/mike_s'}
         metadata.append({'contributor': {'name': con_name,
                                          'organization': con_org, 'email': con_email,
                                          'address': con_address, 'phone': con_phone,
@@ -145,8 +145,8 @@ def test_resource_create_with_core_metadata(self):
         cr_address = "101 Clarson Ave, Provo UT-84321, USA"
         cr_phone = '801-567=9090'
         cr_homepage = 'http://byu.edu/homepage/002'
-        cr_identifiers = {'ORCID': 'https://orcid.org/john_smith',
-                          'ResearchGateID': 'https://www.researchgate.net/john_smith'}
+        cr_identifiers = {'ORCID': 'https://orcid.org/0000-0003-4621-0559',
+                          'ResearchGateID': 'https://www.researchgate.net/profile/john_smith'}
         metadata.append({'creator': {'name': cr_name, 'organization': cr_org,
                                      'email': cr_email, 'address': cr_address,
                                      'phone': cr_phone, 'homepage': cr_homepage,
diff --git a/hs_core/tests/api/rest/test_resource_science_meta.py b/hs_core/tests/api/rest/test_resource_science_meta.py
--- a/hs_core/tests/api/rest/test_resource_science_meta.py
+++ b/hs_core/tests/api/rest/test_resource_science_meta.py
@@ -52,8 +52,8 @@ def test_put_scimeta_composite_resource(self):
             "contributors": [{
                 "name": "Test Name 1",
                 "organization": "Org 1",
-                "identifiers": {"ORCID": "https://orcid.org/012",
-                                "ResearchGateID": "https://www.researchgate.net/002"}
+                "identifiers": {"ORCID": "https://orcid.org/0000-0003-4621-0559",
+                                "ResearchGateID": "https://www.researchgate.net/profile/002"}
             }, {
                 "name": None,
                 "organization": "Org 2"
@@ -64,8 +64,8 @@ def test_put_scimeta_composite_resource(self):
             }, {
                 "name": "Creator 2",
                 "organization": "USU",
-                "identifiers": {"ORCID": "https://orcid.org/011",
-                                "ResearchGateID": "https://www.researchgate.net/001"}
+                "identifiers": {"ORCID": "https://orcid.org/0000-0003-4621-0559",
+                                "ResearchGateID": "https://www.researchgate.net/profile/001"}
             }],
             "coverages": [{
                 "type": "box",
@@ -217,8 +217,8 @@ def test_put_scimeta_composite_resource_with_core_metadata(self):
             "creators": [{
                 "name": "Creator",
                 "organization": None,
-                "identifiers": {"ORCID": "https://orcid.org/011",
-                                "ResearchGateID": "https://www.researchgate.net/001"}
+                "identifiers": {"ORCID": "https://orcid.org/0000-0003-4621-0559",
+                                "ResearchGateID": "https://www.researchgate.net/profile/001"}
             }],
             "dates": [
                 {
@@ -358,8 +358,8 @@ def test_put_scimeta_timeseries_resource_with_core_metadata(self):
             "creators": [{
                 "name": "Creator",
                 "organization": None,
-                "identifiers": {"ORCID": "https://orcid.org/011",
-                                "ResearchGateID": "https://www.researchgate.net/001"}
+                "identifiers": {"ORCID": "https://orcid.org/0000-0003-4621-0559",
+                                "ResearchGateID": "https://www.researchgate.net/profile/001"}
             }],
             "dates": [
                 {
@@ -397,14 +397,14 @@ def test_put_web_app_resource_tool_metadata(self):
             }, {
                 "name": "Test Name 2",
                 "organization": "Org 2",
-                "identifiers": {"ORCID": "https://orcid.org/011",
-                                "ResearchGateID": "https://www.researchgate.net/001"}
+                "identifiers": {"ORCID": "https://orcid.org/0000-0003-4621-0559",
+                                "ResearchGateID": "https://www.researchgate.net/profile/001"}
             }],
             "creators": [{
                 "name": "Creator",
                 "organization": None,
-                "identifiers": {"ORCID": "https://orcid.org/011",
-                                "ResearchGateID": "https://www.researchgate.net/001"}
+                "identifiers": {"ORCID": "https://orcid.org/0000-0003-4621-0559",
+                                "ResearchGateID": "https://www.researchgate.net/profile/001"}
             }],
             "coverages": [{
                 "type": "box",