forked from DSpace/DSpace
-
Notifications
You must be signed in to change notification settings - Fork 23
Expand file tree
/
Copy pathClarinIdentityServiceImpl.java
More file actions
155 lines (137 loc) · 6.06 KB
/
Copy pathClarinIdentityServiceImpl.java
File metadata and controls
155 lines (137 loc) · 6.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.eperson.clarin;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.dao.clarin.EPersonNetidAliasDAO;
import org.dspace.eperson.service.EPersonService;
import org.dspace.eperson.service.clarin.ClarinIdentityService;
import org.dspace.services.ConfigurationService;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @see ClarinIdentityService
*
* @author Ondrej Kosarko
*/
public class ClarinIdentityServiceImpl implements ClarinIdentityService {
private static final Logger log = LogManager.getLogger(ClarinIdentityServiceImpl.class);
@Autowired
private EPersonNetidAliasDAO ePersonNetidAliasDAO;
@Autowired
private ConfigurationService configurationService;
@Autowired
private EPersonService ePersonService;
@Override
public EPerson resolve(Context context, String netid) throws SQLException {
if (StringUtils.isBlank(netid)) {
return null;
}
EPersonNetidAlias alias = ePersonNetidAliasDAO.findByNetid(context, netid);
if (alias != null) {
return alias.getEPerson();
}
// Alias rows exist only for netids that went through attach() or the migration backfill.
// A netid written straight to eperson.netid after the migration (ClarinShibAuthentication#updateEPerson
// locking a first login to its netid) has no alias row yet, so check the legacy column too.
return ePersonService.findByNetid(context, netid);
}
@Override
public EPersonNetidAlias attach(Context context, EPerson ePerson, String netid, String source, EPerson createdBy)
throws SQLException {
// Check-then-insert is racy under concurrent first logins with the same new netid; this is
// accepted: the unique constraint on netid preserves integrity, the losing request fails
// once and succeeds on retry. Recovering in-session (catching the constraint violation)
// is unreliable after a failed flush, so we deliberately don't attempt it.
EPersonNetidAlias existing = ePersonNetidAliasDAO.findByNetid(context, netid);
if (existing != null) {
if (!existing.getEPerson().getID().equals(ePerson.getID())) {
throw new IllegalStateException(
"netid '" + netid + "' is already attached to a different EPerson (" +
existing.getEPerson().getID() + "), refusing to reattach to " + ePerson.getID());
}
return existing;
}
EPersonNetidAlias alias = new EPersonNetidAlias();
alias.setEPerson(ePerson);
alias.setNetid(netid);
alias.setSource(source);
alias.setCreatedBy(createdBy);
alias.setCreatedDate(new Date());
return ePersonNetidAliasDAO.create(context, alias);
}
@Override
public List<EPersonNetidAlias> findAliases(Context context, EPerson ePerson) throws SQLException {
return ePersonNetidAliasDAO.findByEPerson(context, ePerson);
}
@Override
public void detach(Context context, EPersonNetidAlias alias) throws SQLException {
ePersonNetidAliasDAO.delete(context, alias);
}
@Override
public boolean isAllowlistedProxy(String authority) {
if (StringUtils.isBlank(authority)) {
return false;
}
String[] allowlist = configurationService.getArrayProperty("identity.auto-link.proxy-allowlist");
return ArrayUtils.contains(allowlist, authority);
}
@Override
public EPerson autoLink(Context context, List<String> upstreamEppns, String proxyAuthority, String proxyNetid)
throws SQLException {
if (upstreamEppns == null || upstreamEppns.isEmpty() || StringUtils.isBlank(proxyNetid)) {
return null;
}
if (!isAllowlistedProxy(proxyAuthority)) {
log.warn("Auto-link via proxy '{}' refused: proxy is not on the identity.auto-link.proxy-allowlist.",
proxyAuthority);
return null;
}
// Already linked (e.g. concurrent/repeat login racing this method) - nothing to do.
EPerson alreadyLinked = resolve(context, proxyNetid);
if (alreadyLinked != null) {
return alreadyLinked;
}
Set<EPerson> matches = new LinkedHashSet<>();
List<String> matchedOn = new ArrayList<>();
for (String eppn : upstreamEppns) {
if (StringUtils.isBlank(eppn)) {
continue;
}
for (EPersonNetidAlias alias : ePersonNetidAliasDAO.findByValueAnyAuthority(context, eppn)) {
if (matches.add(alias.getEPerson())) {
matchedOn.add(eppn);
}
}
}
if (matches.isEmpty()) {
return null;
}
if (matches.size() > 1) {
throw new AmbiguousIdentityException(
"Auto-link via proxy '" + proxyAuthority + "' matched " + matches.size()
+ " different EPersons for released identities " + matchedOn
+ " (proxy netid '" + proxyNetid + "') - refusing to guess, "
+ "the accounts need an admin merge/link first.");
}
EPerson matched = matches.iterator().next();
attach(context, matched, proxyNetid, SOURCE_AUTO_VOPERSON, null);
log.info("Auto-linked netid '{}' to EPerson {} via proxy '{}' voperson_external_id match.",
proxyNetid, matched.getID(), proxyAuthority);
return matched;
}
}