Skip to content

Commit

Permalink
Customer UK update 15.11.-6.12. (#465)
Browse files Browse the repository at this point in the history
* ufal/downloading-restricted-bitstreams-not-working-properly (#457)

* The admin could download restricted bitstreams.

* The user cannot download bitstream if he/she is not signed in.

* Cannot obtain context with user.

* The user is already fetched from the context.

* Added docs.

* Do not use endpoint for downloading the single file because it already exists - remove it.

* Fixed AuthorizationRestControllerIT tests.

* ufal/be-cannot-download-and-preview-files-after-migration (#454)

* Find bitstream format using mimetype not ID.

* Updated tests according to fix.

* ufal/update-canonical-prefix-to-hdl (#460)

* Updated `handle.canonical.prefix` to hdl handle.

* Integration test cannot have changed handle.canonical.prefix.

* ufal/be-user-registration-missing (#463)

* The user registration is added when the eperson is created by the ui.

* try using newer checkout, same as upstream, since error is with git

---------

Co-authored-by: MajoBerger <[email protected]>

* ufal/be-license-download-statistics (#462)

* Add logging of downloading restricted bitstreams.

* Log downloading of every bitstream not only restricted ones.

* Added docs

* Refactored logging.

* ufal/be-shibboleth-headers-encoding (#464)

* givenname and last name values are converted to UTF-8 encoding

* Delete eperson in tests

---------

Co-authored-by: MajoBerger <[email protected]>
  • Loading branch information
milanmajchrak and MajoBerger authored Dec 6, 2023
1 parent 6034d69 commit 6619764
Show file tree
Hide file tree
Showing 10 changed files with 201 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
package org.dspace.app.statistics.clarin;

import java.sql.SQLException;
import java.text.MessageFormat;
import java.util.List;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
Expand All @@ -22,6 +23,7 @@
import org.dspace.content.service.ItemService;
import org.dspace.content.service.clarin.ClarinItemService;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.matomo.java.tracking.CustomVariable;
Expand Down Expand Up @@ -132,6 +134,24 @@ public void trackBitstreamDownload(Context context, HttpServletRequest request,
return;
}

// Log the user which is downloading the bitstream
this.logUserDownloadingBitstream(context, bit);
// Track the bitstream downloading event
trackPage(context, request, item, "Bitstream Download / Single File");
}

/**
* Log the user which is downloading the bitstream
* @param context DSpace context object
* @param bit Bitstream which is downloading
*/
private void logUserDownloadingBitstream(Context context, Bitstream bit) {
EPerson eperson = context.getCurrentUser();
String pattern = "The user name: {0}, uuid: {1} is downloading bitstream name: {2}, uuid: {3}.";
String logMessage = Objects.isNull(eperson)
? MessageFormat.format(pattern, "ANONYMOUS", "null", bit.getName(), bit.getID())
: MessageFormat.format(pattern, eperson.getFullName(), eperson.getID(), bit.getName(), bit.getID());

log.info(logMessage);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -695,8 +695,8 @@ protected EPerson registerNewEPerson(Context context, HttpServletRequest request
// Header values
String netid = Util.formatNetId(findSingleAttribute(request, netidHeader), org);
String email = findSingleAttribute(request, emailHeader);
String fname = findSingleAttribute(request, fnameHeader);
String lname = findSingleAttribute(request, lnameHeader);
String fname = Headers.updateValueByCharset(findSingleAttribute(request, fnameHeader));
String lname = Headers.updateValueByCharset(findSingleAttribute(request, lnameHeader));

// If the values are not in the request headers try to retrieve it from `shibheaders`.
if (StringUtils.isEmpty(netid)) {
Expand Down Expand Up @@ -817,8 +817,8 @@ protected void updateEPerson(Context context, HttpServletRequest request, EPerso

String netid = Util.formatNetId(findSingleAttribute(request, netidHeader), shibheaders.get_idp());
String email = findSingleAttribute(request, emailHeader);
String fname = findSingleAttribute(request, fnameHeader);
String lname = findSingleAttribute(request, lnameHeader);
String fname = Headers.updateValueByCharset(findSingleAttribute(request, fnameHeader));
String lname = Headers.updateValueByCharset(findSingleAttribute(request, lnameHeader));

// If the values are not in the request headers try to retrieve it from `shibheaders`.
if (StringUtils.isEmpty(netid)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,19 @@
package org.dspace.authenticate.clarin;

import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.services.ConfigurationService;
import org.dspace.utils.DSpace;

/**
* Helper class for request headers.
Expand All @@ -29,9 +33,11 @@ public class Headers {
private static final Logger log = LogManager.getLogger(org.dspace.authenticate.clarin.Headers.class);
// variables
//
private static ConfigurationService configurationService = new DSpace().getConfigurationService();

private HashMap<String, List<String>> headers_ = new HashMap<String, List<String>>();
private String header_separator_ = null;
private static String EMPTY_STRING = "";


// ctors
Expand Down Expand Up @@ -157,17 +163,53 @@ private List<String> header2values(String header) {


/**
* Convert ISO header value to UTF-8
* @param value ISO header value String
* @return
* Convert ISO header value to UTF-8 or return UTF-8 value if it is not ISO.
* @param value ISO/UTF-8 header value String
* @return Converted ISO value to UTF-8 or UTF-8 value from input
*/
private String updateValueByCharset(String value) {
public static String updateValueByCharset(String value) {
String inputEncoding = configurationService.getProperty("shibboleth.name.conversion.inputEncoding",
"ISO-8859-1");
String outputEncoding = configurationService.getProperty("shibboleth.name.conversion.outputEncoding",
"UTF-8");

if (StringUtils.isBlank(value)) {
value = EMPTY_STRING;
}

// If the value is not ISO-8859-1, then it is already UTF-8
if (!isISOType(value)) {
return value;
}

try {
return new String(value.getBytes("ISO-8859-1"), "UTF-8");
// Encode the string to UTF-8
return new String(value.getBytes(inputEncoding), outputEncoding);
} catch (UnsupportedEncodingException ex) {
log.warn("Failed to reconvert shibboleth attribute with value ("
+ value + ").", ex);
log.warn("Cannot convert the value: " + value + " from " + inputEncoding + " to " + outputEncoding +
" because of: " + ex.getMessage());
return value;
}
}

/**
* Check if the value is ISO-8859-1 encoded.
* @param value String to check
* @return true if the value is ISO-8859-1 encoded, false otherwise
*/
private static boolean isISOType(String value) {
try {
// Encode the string to ISO-8859-1
byte[] iso8859Bytes = value.getBytes(StandardCharsets.ISO_8859_1);

// Decode the bytes back to a string using ISO-8859-1
String decodedString = new String(iso8859Bytes, StandardCharsets.ISO_8859_1);

// Compare the original string with the decoded string
return StringUtils.equals(value, decodedString);
} catch (Exception e) {
// An exception occurred, so the input is not ISO-8859-1
return false;
}
return value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,12 @@
@Table(name = "user_registration")
public class ClarinUserRegistration implements ReloadableEntity<Integer> {

// Anonymous user
public static final String ANONYMOUS_USER_REGISTRATION = "anonymous";

// Registered user without organization
public static final String UNKNOWN_USER_REGISTRATION = "Unknown";

private static Logger log = org.apache.logging.log4j.LogManager.getLogger(ClarinUserRegistration.class);

@Id
Expand Down
2 changes: 2 additions & 0 deletions dspace-api/src/test/data/dspaceFolder/config/local.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -271,3 +271,5 @@ lr.pid.community.configurations = community=*, prefix=123456789, type=local, can

#### Authority configuration `authority.cfg`
authority.controlled.dc.relation = true

handle.canonical.prefix = ${dspace.ui.url}/handle/
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
*/
package org.dspace.app.rest.repository;

import static org.dspace.content.clarin.ClarinUserRegistration.UNKNOWN_USER_REGISTRATION;

import java.io.IOException;
import java.sql.SQLException;
import java.util.Arrays;
Expand Down Expand Up @@ -35,6 +37,8 @@
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.authorize.service.ValidatePasswordService;
import org.dspace.content.clarin.ClarinUserRegistration;
import org.dspace.content.service.clarin.ClarinUserRegistrationService;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.EmptyWorkflowGroupException;
Expand Down Expand Up @@ -79,6 +83,9 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository<EPerson, E
@Autowired
private RegistrationDataService registrationDataService;

@Autowired
ClarinUserRegistrationService clarinUserRegistrationService;

private final EPersonService es;


Expand Down Expand Up @@ -135,6 +142,14 @@ private EPerson createEPersonFromRestObject(Context context, EPersonRest eperson
}
es.update(context, eperson);
metadataConverter.setMetadata(context, eperson, epersonRest.getMetadata());

// Create user registration
ClarinUserRegistration clarinUserRegistration = new ClarinUserRegistration();
clarinUserRegistration.setOrganization(UNKNOWN_USER_REGISTRATION);
clarinUserRegistration.setConfirmation(true);
clarinUserRegistration.setEmail(eperson.getEmail());
clarinUserRegistration.setPersonID(eperson.getID());
clarinUserRegistrationService.create(context, clarinUserRegistration);
} catch (SQLException e) {
throw new RuntimeException(e.getMessage(), e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
import org.dspace.app.rest.model.patch.ReplaceOperation;
import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
import org.dspace.app.rest.test.MetadataPatchSuite;
import org.dspace.builder.ClarinUserRegistrationBuilder;
import org.dspace.builder.CollectionBuilder;
import org.dspace.builder.CommunityBuilder;
import org.dspace.builder.EPersonBuilder;
Expand Down Expand Up @@ -120,6 +121,8 @@ public void createTest() throws Exception {

AtomicReference<UUID> idRef = new AtomicReference<UUID>();
AtomicReference<UUID> idRefNoEmbeds = new AtomicReference<UUID>();
AtomicReference<Integer> idRefUserDataReg = new AtomicReference<Integer>();
AtomicReference<Integer> idRefUserDataFullReg = new AtomicReference<Integer>();

String authToken = getAuthToken(admin.getEmail(), password);

Expand Down Expand Up @@ -155,11 +158,51 @@ public void createTest() throws Exception {
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$", HalMatcher.matchNoEmbeds()))
.andDo(result -> idRefNoEmbeds
.set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))));;
.set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))));

// Check that the user registration for test data user has been created
getClient(authToken).perform(get("/api/core/clarinuserregistration/search/byEPerson")
.param("userUUID", String.valueOf(idRef.get()))
.contentType(contentType))
.andExpect(status().isOk())
.andExpect(jsonPath("$.page.totalElements", is(1)))
.andExpect(jsonPath(
"$._embedded.clarinuserregistrations[0].id", is(not(empty()))))
.andExpect(jsonPath(
"$._embedded.clarinuserregistrations[0].email", is("[email protected]")))
.andExpect(jsonPath(
"$._embedded.clarinuserregistrations[0].confirmation", is(true)))
.andExpect(jsonPath(
"$._embedded.clarinuserregistrations[0].ePersonID", is(idRef.get().toString())))
.andDo(result -> idRefUserDataReg
.set(read(result.getResponse().getContentAsString(),
"$._embedded.clarinuserregistrations[0].id")));

// Check that the user registration for test data full user has been created
getClient(authToken).perform(get("/api/core/clarinuserregistration/search/byEPerson")
.param("userUUID", String.valueOf(idRefNoEmbeds.get()))
.contentType(contentType))
.andExpect(status().isOk())
.andExpect(jsonPath("$.page.totalElements", is(1)))
.andExpect(jsonPath(
"$._embedded.clarinuserregistrations[0].id", is(not(empty()))))
.andExpect(jsonPath(
"$._embedded.clarinuserregistrations[0].email",
is("[email protected]")))
.andExpect(jsonPath(
"$._embedded.clarinuserregistrations[0].confirmation", is(true)))
.andExpect(jsonPath(
"$._embedded.clarinuserregistrations[0].ePersonID",
is(idRefNoEmbeds.get().toString())))
.andDo(result -> idRefUserDataFullReg
.set(read(result.getResponse().getContentAsString(),
"$._embedded.clarinuserregistrations[0].id")));

} finally {
EPersonBuilder.deleteEPerson(idRef.get());
EPersonBuilder.deleteEPerson(idRefNoEmbeds.get());
ClarinUserRegistrationBuilder.deleteClarinUserRegistration(idRefUserDataReg.get());
ClarinUserRegistrationBuilder.deleteClarinUserRegistration(idRefUserDataFullReg.get());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -446,15 +446,18 @@ public void testRedirectToGivenUntrustedUrl() throws Exception {
}

@Test
public void testUTF8ShibHeaders() throws Exception {
public void testISOShibHeaders() throws Exception {
String testMail = "[email protected]";
String testIdp = IDP_TEST_EPERSON + "test";
String testNetId = NET_ID_TEST_EPERSON + "000";
// NOTE: The initial call to /shibboleth comes *from* an external Shibboleth site. So, it is always
// unauthenticated, but it must include some expected SHIB attributes.
// SHIB-MAIL attribute is the default email header sent from Shibboleth after a successful login.
// In this test we are simply mocking that behavior by setting it to an existing EPerson.
String token = getClient().perform(get("/api/authn/shibboleth")
.header("SHIB-MAIL", clarinEperson.getEmail())
.header("Shib-Identity-Provider", IDP_TEST_EPERSON)
.header("SHIB-NETID", NET_ID_TEST_EPERSON)
.header("SHIB-MAIL", testMail)
.header("Shib-Identity-Provider", testIdp)
.header("SHIB-NETID", testNetId)
.header("SHIB-GIVENNAME", "knihovna KůÅ\u0088 test ŽluÅ¥ouÄ\u008Dký"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("http://localhost:4000"))
Expand All @@ -466,6 +469,56 @@ public void testUTF8ShibHeaders() throws Exception {
.andExpect(jsonPath("$.authenticated", is(true)))
.andExpect(jsonPath("$.authenticationMethod", is("shibboleth")));

// Check if was created a user with such email and netid.
EPerson ePerson = ePersonService.findByNetid(context, Util.formatNetId(testNetId, testIdp));
assertTrue(Objects.nonNull(ePerson));
assertEquals(ePerson.getEmail(), testMail);
assertEquals(ePerson.getFirstName(), "knihovna Kůň test Žluťoučký");

EPersonBuilder.deleteEPerson(ePerson.getID());

getClient(token).perform(
get("/api/authz/authorizations/search/object")
.param("embed", "feature")
.param("feature", feature)
.param("uri", utils.linkToSingleResource(ePersonRest, "self").getHref()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.page.totalElements", is(0)))
.andExpect(jsonPath("$._embedded").doesNotExist());
}

@Test
public void testUTF8ShibHeaders() throws Exception {
String testMail = "[email protected]";
String testIdp = IDP_TEST_EPERSON + "test";
String testNetId = NET_ID_TEST_EPERSON + "000";
// NOTE: The initial call to /shibboleth comes *from* an external Shibboleth site. So, it is always
// unauthenticated, but it must include some expected SHIB attributes.
// SHIB-MAIL attribute is the default email header sent from Shibboleth after a successful login.
// In this test we are simply mocking that behavior by setting it to an existing EPerson.
String token = getClient().perform(get("/api/authn/shibboleth")
.header("SHIB-MAIL", testMail)
.header("Shib-Identity-Provider", testIdp)
.header("SHIB-NETID", testNetId)
.header("SHIB-GIVENNAME", "knihovna Kůň test Žluťoučký"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("http://localhost:4000"))
.andReturn().getResponse().getHeader("Authorization");


getClient(token).perform(get("/api/authn/status"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.authenticated", is(true)))
.andExpect(jsonPath("$.authenticationMethod", is("shibboleth")));

// Check if was created a user with such email and netid.
EPerson ePerson = ePersonService.findByNetid(context, Util.formatNetId(testNetId, testIdp));
assertTrue(Objects.nonNull(ePerson));
assertEquals(ePerson.getEmail(), testMail);
assertEquals(ePerson.getFirstName(), "knihovna Kůň test Žluťoučký");

EPersonBuilder.deleteEPerson(ePerson.getID());

getClient(token).perform(
get("/api/authz/authorizations/search/object")
.param("embed", "feature")
Expand Down
4 changes: 4 additions & 0 deletions dspace/config/clarin-dspace.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -224,3 +224,7 @@ themed.by.company.name = dataquest s.r.o.
#### Authority configuration `authority.cfg`
## dc.relation authority is configured only because of correct item importing, but it is not used anymore.
authority.controlled.dc.relation = true

#nameConversion
shibboleth.name.conversion.inputEncoding = ISO-8859-1
shibboleth.name.conversion.outputEncoding = UTF-8
2 changes: 1 addition & 1 deletion dspace/config/dspace.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ identifier.doi.namespaceseparator = dspace/
#
# Items in DSpace receive a unique URL, stored in dc.identifier.uri
# after it is generated during the submission process.
handle.canonical.prefix = ${dspace.ui.url}/handle/
handle.canonical.prefix = http://hdl.handle.net/

# If you register with CNRI's handle service at https://www.handle.net/,
# these links can be generated as permalinks using https://hdl.handle.net/
Expand Down

0 comments on commit 6619764

Please sign in to comment.