Skip to content

Commit

Permalink
Fixed conflicts
Browse files Browse the repository at this point in the history
  • Loading branch information
milanmajchrak committed Nov 4, 2024
2 parents ff983b3 + 68fb203 commit 99b9df9
Show file tree
Hide file tree
Showing 24 changed files with 780 additions and 277 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,7 @@ The full license is available in the [LICENSE](LICENSE) file or online at http:/

DSpace uses third-party libraries which may be distributed under different licenses. Those licenses are listed
in the [LICENSES_THIRD_PARTY](LICENSES_THIRD_PARTY) file.


# Additional notes

This project is tested with BrowserStack.
36 changes: 30 additions & 6 deletions dspace-api/src/main/java/org/dspace/app/util/DCInput.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.xml.sax.SAXException;

Expand Down Expand Up @@ -257,12 +258,11 @@ public DCInput(Map<String, String> fieldMap, Map<String, List<String>> listMap,
// parsing of the <type-bind> element (using the colon as split separator)
typeBind = new ArrayList<String>();
String typeBindDef = fieldMap.get("type-bind");
if (typeBindDef != null && typeBindDef.trim().length() > 0) {
String[] types = typeBindDef.split(",");
for (String type : types) {
typeBind.add(type.trim());
}
}
this.insertToTypeBind(typeBindDef);
String typeBindField = fieldMap.get(DCInputsReader.TYPE_BIND_FIELD_ATTRIBUTE);
this.insertToTypeBind(typeBindField);


style = fieldMap.get("style");
isRelationshipField = fieldMap.containsKey("relationship-type");
isMetadataField = fieldMap.containsKey("dc-schema");
Expand All @@ -281,6 +281,15 @@ public DCInput(Map<String, String> fieldMap, Map<String, List<String>> listMap,

}

private void insertToTypeBind(String typeBindDef) {
if (StringUtils.isNotEmpty(typeBindDef)) {
String[] types = typeBindDef.split(",");
for (String type : types) {
typeBind.add(type.trim());
}
}
}

protected void initRegex(String regex) {
this.regex = null;
this.pattern = null;
Expand Down Expand Up @@ -567,6 +576,21 @@ public boolean isAllowedFor(String typeName) {
return typeBind.contains(typeName);
}

/**
* Decides if this field is valid for the document type
* Check if one of the typeName is in the typeBind list
*
* @param typeNames List of document type names e.g. ["VIDEO"]
* @return true when there is no type restriction or typeName is allowed
*/
public boolean isAllowedFor(List<String> typeNames) {
if (typeBind.isEmpty()) {
return true;
}

return CollectionUtils.containsAny(typeBind, typeNames);
}

public String getScope() {
return visibility;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public class DCInputsReader {
* Keyname for storing the name of the complex input type
*/
static final String COMPLEX_DEFINITION_REF = "complex-definition-ref";
public static final String TYPE_BIND_FIELD_ATTRIBUTE = "field";

/**
* Keyname for storing the name of the custom autocomplete input type
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.net.URLEncoder;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
Expand All @@ -19,13 +20,13 @@
import java.util.Objects;
import java.util.UUID;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.app.util.Util;
import org.dspace.authenticate.AuthenticationMethod;
import org.dspace.authenticate.factory.AuthenticateServiceFactory;
import org.dspace.authorize.AuthorizeException;
Expand Down Expand Up @@ -247,23 +248,24 @@ public int authenticate(Context context, String username, String password,

// Should we auto register new users.
boolean autoRegister = configurationService.getBooleanProperty("authentication-shibboleth.autoregister", true);
String[] netidHeaders = configurationService.getArrayProperty("authentication-shibboleth.netid-header");

// Four steps to authenticate a user
try {
// Step 1: Identify User
EPerson eperson = findEPerson(context, request);
EPerson eperson = findEPerson(context, request, netidHeaders);

// Step 2: Register New User, if necessary
if (eperson == null && autoRegister && !isDuplicateUser) {
eperson = registerNewEPerson(context, request);
eperson = registerNewEPerson(context, request, netidHeaders);
}

if (eperson == null) {
return AuthenticationMethod.NO_SUCH_USER;
}

// Step 3: Update User's Metadata
updateEPerson(context, request, eperson);
updateEPerson(context, request, eperson, netidHeaders);

// Step 4: Log the user in.
context.setCurrentUser(eperson);
Expand Down Expand Up @@ -540,11 +542,11 @@ public static boolean isEnabled() {
* @throws SQLException if database error
* @throws AuthorizeException if authorization error
*/
protected EPerson findEPerson(Context context, HttpServletRequest request) throws SQLException, AuthorizeException {
protected EPerson findEPerson(Context context, HttpServletRequest request, String[] netidHeaders)
throws SQLException {

boolean isUsingTomcatUser = configurationService
.getBooleanProperty("authentication-shibboleth.email-use-tomcat-remote-user");
String netidHeader = configurationService.getProperty("authentication-shibboleth.netid-header");
String emailHeader = configurationService.getProperty("authentication-shibboleth.email-header");

EPerson eperson = null;
Expand All @@ -554,26 +556,10 @@ protected EPerson findEPerson(Context context, HttpServletRequest request) throw


// 1) First, look for a netid header.
if (netidHeader != null) {
String org = shibheaders.get_idp();
String netid = Util.formatNetId(findSingleAttribute(request, netidHeader), org);
if (StringUtils.isEmpty(netid)) {
netid = shibheaders.get_single(netidHeader);
}

if (netid != null) {
if (netidHeaders != null) {
eperson = findEpersonByNetId(netidHeaders, shibheaders, eperson, ePersonService, context, true);
if (eperson != null) {
foundNetID = true;
eperson = ePersonService.findByNetid(context, netid);

if (eperson == null) {
log.info(
"Unable to identify EPerson based upon Shibboleth netid header: '" + netidHeader + "'='" +
netid + "'.");
} else {
log.debug(
"Identified EPerson based upon Shibboleth netid header: '" + netidHeader + "'='" +
netid + "'" + ".");
}
}
}

Expand Down Expand Up @@ -656,7 +642,6 @@ protected EPerson findEPerson(Context context, HttpServletRequest request) throw
return eperson;
}


/**
* Register a new eperson object. This method is called when no existing user was
* found for the NetID or Email and autoregister is enabled. When these conditions
Expand All @@ -677,11 +662,10 @@ protected EPerson findEPerson(Context context, HttpServletRequest request) throw
* @throws SQLException if database error
* @throws AuthorizeException if authorization error
*/
protected EPerson registerNewEPerson(Context context, HttpServletRequest request)
protected EPerson registerNewEPerson(Context context, HttpServletRequest request, String[] netidHeaders)
throws SQLException, AuthorizeException {

// Header names
String netidHeader = configurationService.getProperty("authentication-shibboleth.netid-header");
String emailHeader = configurationService.getProperty("authentication-shibboleth.email-header");
String fnameHeader = configurationService.getProperty("authentication-shibboleth.firstname-header");
String lnameHeader = configurationService.getProperty("authentication-shibboleth.lastname-header");
Expand All @@ -694,15 +678,12 @@ protected EPerson registerNewEPerson(Context context, HttpServletRequest request
// CLARIN

// Header values
String netid = Util.formatNetId(findSingleAttribute(request, netidHeader), org);
String netid = getFirstNetId(netidHeaders);
String email = getEmailAcceptedOrNull(findSingleAttribute(request, emailHeader));
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)) {
netid = shibheaders.get_single(netidHeader);
}
if (StringUtils.isEmpty(email) && Objects.nonNull(clarinVerificationToken)) {
email = clarinVerificationToken.getEmail();
}
Expand All @@ -718,7 +699,7 @@ protected EPerson registerNewEPerson(Context context, HttpServletRequest request
// don't have at least these three pieces of information then we fail.
String message = "Unable to register new eperson because we are unable to find an email address along " +
"with first and last name for the user.\n";
message += " NetId Header: '" + netidHeader + "'='" + netid + "' (Optional) \n";
message += " NetId Header: '" + Arrays.toString(netidHeaders) + "'='" + netid + "' (Optional) \n";
message += " Email Header: '" + emailHeader + "'='" + email + "' \n";
message += " First Name Header: '" + fnameHeader + "'='" + fname + "' \n";
message += " Last Name Header: '" + lnameHeader + "'='" + lname + "'";
Expand Down Expand Up @@ -807,24 +788,20 @@ protected EPerson registerNewEPerson(Context context, HttpServletRequest request
* @throws SQLException if database error
* @throws AuthorizeException if authorization error
*/
protected void updateEPerson(Context context, HttpServletRequest request, EPerson eperson)
protected void updateEPerson(Context context, HttpServletRequest request, EPerson eperson, String[] netidHeaders)
throws SQLException, AuthorizeException {

// Header names & values
String netidHeader = configurationService.getProperty("authentication-shibboleth.netid-header");
String emailHeader = configurationService.getProperty("authentication-shibboleth.email-header");
String fnameHeader = configurationService.getProperty("authentication-shibboleth.firstname-header");
String lnameHeader = configurationService.getProperty("authentication-shibboleth.lastname-header");

String netid = Util.formatNetId(findSingleAttribute(request, netidHeader), shibheaders.get_idp());
String netid = getFirstNetId(netidHeaders);
String email = getEmailAcceptedOrNull(findSingleAttribute(request, emailHeader));
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)) {
netid = shibheaders.get_single(netidHeader);
}
if (StringUtils.isEmpty(email) && Objects.nonNull(clarinVerificationToken)) {
email = clarinVerificationToken.getEmail();
}
Expand Down Expand Up @@ -858,7 +835,16 @@ protected void updateEPerson(Context context, HttpServletRequest request, EPerso
}
// The email could have changed if using netid based lookup.
if (email != null) {
eperson.setEmail(email.toLowerCase());
String lowerCaseEmail = email.toLowerCase();
// Check the email is unique
EPerson epersonByEmail = ePersonService.findByEmail(context, lowerCaseEmail);
if (epersonByEmail != null && !epersonByEmail.getID().equals(eperson.getID())) {
log.error("Unable to update the eperson's email metadata because the email '{}' is already in use.",
lowerCaseEmail);
throw new AuthorizeException("The email address is already in use.");
} else {
eperson.setEmail(email.toLowerCase());
}
}
if (fname != null) {
eperson.setFirstName(context, fname);
Expand Down Expand Up @@ -1207,29 +1193,11 @@ public String findSingleAttribute(HttpServletRequest request, String name) {
if (name == null) {
return null;
}

String value = findAttribute(request, name);


if (value != null) {
// If there are multiple values encoded in the shibboleth attribute
// they are separated by a semicolon, and any semicolons in the
// attribute are escaped with a backslash. For this case we are just
// looking for the first attribute so we scan the value until we find
// the first unescaped semicolon and chop off everything else.
int idx = 0;
do {
idx = value.indexOf(';', idx);
if (idx != -1 && value.charAt(idx - 1) != '\\') {
value = value.substring(0, idx);
break;
}
} while (idx >= 0);

// Unescape the semicolon after splitting
value = value.replaceAll("\\;", ";");
value = sortEmailsAndGetFirst(value);
}

return value;
}

Expand Down Expand Up @@ -1338,5 +1306,70 @@ public String getEmailAcceptedOrNull(String email) {
}
return email;
}

/**
* Find an EPerson by a NetID header. The method will go through all the netid headers and try to find a user.
*/
public static EPerson findEpersonByNetId(String[] netidHeaders, ShibHeaders shibheaders, EPerson eperson,
EPersonService ePersonService, Context context, boolean logAllowed)
throws SQLException {
// Go through all the netid headers and try to find a user. It could be e.g., `eppn`, `persistent-id`,..
for (String netidHeader : netidHeaders) {
netidHeader = netidHeader.trim();
String netid = shibheaders.get_single(netidHeader);
if (netid == null) {
continue;
}

eperson = ePersonService.findByNetid(context, netid);

if (eperson == null && logAllowed) {
log.info(
"Unable to identify EPerson based upon Shibboleth netid header: '" + netidHeader +
"'='" + netid + "'.");
} else {
log.debug(
"Identified EPerson based upon Shibboleth netid header: '" + netidHeader + "'='" +
netid + "'" + ".");
}
}
return eperson;
}

/**
* Sort the email addresses and return the first one.
* @param value The email addresses separated by semicolons.
*/
public static String sortEmailsAndGetFirst(String value) {
// If there are multiple values encoded in the shibboleth attribute
// they are separated by a semicolon, and any semicolons in the
// attribute are escaped with a backslash.
// Step 1: Split the input string into email addresses
List<String> emails = Arrays.stream(value.split("(?<!\\\\);")) // Split by unescaped semicolon
.map(email -> email.replaceAll("\\\\;", ";")) // Unescape semicolons
.collect(Collectors.toList());

// Step 2: Sort the email list alphabetically
emails.sort(String::compareToIgnoreCase);

// Step 3: Get the first sorted email
return emails.get(0);
}

/**
* Get the first netid from the list of netid headers. E.g., eppn, persistent-id,...
* @param netidHeaders list of netid headers loaded from the configuration `authentication-shibboleth.netid-header`
*/
public String getFirstNetId(String[] netidHeaders) {
for (String netidHeader : netidHeaders) {
netidHeader = netidHeader.trim();
String netid = shibheaders.get_single(netidHeader);
if (netid != null) {
//When creating use first match (eppn before targeted-id)
return netid;
}
}
return null;
}
}

Loading

0 comments on commit 99b9df9

Please sign in to comment.