Skip to content

Commit 327af2f

Browse files
author
Jenkins CI
committed
Merge branch 'dev-3.6'
2 parents f99253e + d994c20 commit 327af2f

File tree

19 files changed

+724
-108
lines changed

19 files changed

+724
-108
lines changed

documentation/src/main/doc/input-translation.txt

+4-2
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,10 @@ MVEL context reference:
9595
is multi-valued then the first value is returned. If the attribute has no value then empty string is returned.
9696
. +attrs+ Map indexed with attribute names. Value of each entry is a list of the attribute values.
9797
. +id+ Value of the authenticated identity. If remote IdP returned multiple identities, then a random one is available,
98-
though this is a very exotic case.
99-
. +idType+ The type of the identity stored in the +id+ variable.
98+
though this is a very exotic case. In rare cases (typically with SMAL IdPs) it may happen that IdP is not providing any
99+
unique identifier of the identity. In such case this variable is set to +null+, and user should be identified
100+
by some (combination of) attributes.
101+
. +idType+ The type of the identity stored in the +id+ variable. +null+ only if +id+ is null.
100102
. +idsByType+ Map of identity values indexed by type. Rarely useful.
101103
. +groups+ List of all remote groups.
102104

engine-api/src/main/java/pl/edu/icm/unity/engine/api/translation/in/InputTranslationContextFactory.java

+4
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ public static Map<String, Object> createMvelContext(RemotelyAuthenticatedInput i
5050
RemoteIdentity ri = input.getIdentities().values().iterator().next();
5151
ret.put(ContextKey.id.name(), ri.getName());
5252
ret.put(ContextKey.idType.name(), ri.getIdentityType());
53+
} else
54+
{
55+
ret.put(ContextKey.id.name(), null);
56+
ret.put(ContextKey.idType.name(), null);
5357
}
5458

5559
Map<String, List<String>> idsByType = new HashMap<String, List<String>>();

engine/src/main/java/pl/edu/icm/unity/engine/forms/enquiry/EnquiryManagementImpl.java

+5-2
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import pl.edu.icm.unity.engine.forms.RegistrationConfirmationSupport;
4545
import pl.edu.icm.unity.engine.forms.RegistrationConfirmationSupport.Phase;
4646
import pl.edu.icm.unity.exceptions.EngineException;
47+
import pl.edu.icm.unity.exceptions.IllegalIdentityValueException;
4748
import pl.edu.icm.unity.exceptions.WrongArgumentException;
4849
import pl.edu.icm.unity.stdext.attr.StringAttribute;
4950
import pl.edu.icm.unity.store.api.generic.EnquiryFormDB;
@@ -497,14 +498,16 @@ private void addToAttribute(long entityId, String attributeName, String value) t
497498
dbAttributes.addAttribute(entityId, attribute, true, false);
498499
}
499500

500-
private void removeAllPendingRequestsOfForm(String enquiryId, EntityParam entity)
501+
private void removeAllPendingRequestsOfForm(String enquiryId, EntityParam entity) throws IllegalIdentityValueException
501502
{
502503
for (EnquiryResponseState en : requestDB.getAll())
503504
{
504505
if (!en.getStatus().equals(RegistrationRequestStatus.pending))
505506
continue;
506507
EnquiryResponse res = en.getRequest();
507-
if (res.getFormId().equals(enquiryId))
508+
long entityId = identitiesResolver.getEntityId(entity);
509+
510+
if (res.getFormId().equals(enquiryId) && en.getEntityId() == entityId)
508511
{
509512
requestDB.delete(en.getRequestId());
510513
}

engine/src/test/java/pl/edu/icm/unity/engine/forms/enquiry/TestStickyEnquiries.java

+40
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,46 @@ public void shouldOverwriteSubmitedRequest() throws Exception
208208
assertThat(res.getRequest().getGroupSelections().get(1).getSelectedGroups().get(0), is("/B"));
209209
}
210210

211+
@Test
212+
public void shouldRemovePendingRequestOnlyForGivenEntity() throws Exception
213+
{
214+
215+
initAndCreateEnquiry("false");
216+
EnquiryResponse response = new EnquiryResponseBuilder()
217+
.withFormId("sticky")
218+
.withAddedGroupSelection()
219+
.withGroup("/")
220+
.withGroup("/A")
221+
.endGroupSelection()
222+
.withAddedGroupSelection()
223+
.withGroup("/B")
224+
.endGroupSelection()
225+
.withAddedAttribute(null)
226+
.build();
227+
228+
Identity addEntity1 = idsMan.addEntity(new IdentityParam(UsernameIdentity.ID, "tuser"),
229+
CRED_REQ_PASS, EntityState.valid);
230+
Identity addEntity2 = idsMan.addEntity(new IdentityParam(UsernameIdentity.ID, "tuser2"),
231+
CRED_REQ_PASS, EntityState.valid);
232+
233+
234+
setupUserContext("tuser", null);
235+
236+
enquiryManagement.submitEnquiryResponse(response,
237+
new RegistrationContext(false, TriggeringMode.manualStandalone));
238+
239+
setupUserContext("tuser2", null);
240+
241+
enquiryManagement.submitEnquiryResponse(response,
242+
new RegistrationContext(false, TriggeringMode.manualStandalone));
243+
244+
setupAdmin();
245+
enquiryManagement.removePendingStickyRequest("sticky", new EntityParam(addEntity1.getEntityId()));
246+
247+
assertThat(enquiryManagement.getEnquiryResponses().stream().filter(e -> e.getEntityId() == addEntity1.getEntityId()).count(), is(0L));
248+
assertThat(enquiryManagement.getEnquiryResponses().stream().filter(e -> e.getEntityId() == addEntity2.getEntityId()).count(), is(1L));
249+
}
250+
211251
@Test
212252
public void shouldBlockMultiSelectGroupInSingleSelectGroupParam() throws Exception
213253
{

pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,7 @@
468468
<dependency>
469469
<groupId>io.imunity.samly</groupId>
470470
<artifactId>samly2</artifactId>
471-
<version>2.7.0</version>
471+
<version>2.7.1</version>
472472
</dependency>
473473
<dependency>
474474
<groupId>io.imunity.samly</groupId>

saml/src/main/java/pl/edu/icm/unity/saml/SAMLResponseValidatorUtil.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ private List<RemoteIdentity> getAuthenticatedIdentities(SSOAuthnResponseValidato
120120
for (int i=0; i<authnAssertions.size(); i++)
121121
{
122122
NameIDType samlName = authnAssertions.get(i).getAssertion().getSubject().getNameID();
123-
ret.add(new RemoteIdentity(samlName.getStringValue(), samlName.getFormat()));
123+
if (samlName != null && !samlName.isNil())
124+
ret.add(new RemoteIdentity(samlName.getStringValue(), samlName.getFormat()));
124125
}
125126
return ret;
126127
}
@@ -141,7 +142,7 @@ private void addSessionParticipants(SSOAuthnResponseValidator validator,
141142
for (AuthnStatementType authNStatement: authNAss.getAuthnStatementArray())
142143
{
143144
sessionIndex = authNStatement.getSessionIndex();
144-
if (sessionIndex != null)
145+
if (sessionIndex != null && authNAss.getSubject().getNameID() != null)
145146
{
146147
SAMLSessionParticipant participant = new SAMLSessionParticipant(
147148
issuer.getStringValue(),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
* Copyright (c) 2014 ICM Uniwersytet Warszawski All rights reserved.
3+
* See LICENCE.txt file for licensing information.
4+
*/
5+
package pl.edu.icm.unity.saml.sp;
6+
7+
import java.net.URL;
8+
import java.util.Optional;
9+
10+
import org.apache.logging.log4j.Logger;
11+
import org.apache.xmlbeans.XmlException;
12+
import org.springframework.beans.factory.annotation.Autowired;
13+
14+
import eu.unicore.samly2.SAMLBindings;
15+
import eu.unicore.samly2.validators.ReplayAttackChecker;
16+
import pl.edu.icm.unity.base.utils.Log;
17+
import pl.edu.icm.unity.engine.api.authn.AuthenticationResult;
18+
import pl.edu.icm.unity.engine.api.authn.AuthenticationResult.ResolvableError;
19+
import pl.edu.icm.unity.engine.api.authn.RemoteAuthenticationException;
20+
import pl.edu.icm.unity.engine.api.authn.RemoteAuthenticationResult;
21+
import pl.edu.icm.unity.engine.api.authn.remote.RedirectedAuthnState;
22+
import pl.edu.icm.unity.engine.api.authn.remote.RemoteAuthnResultTranslator;
23+
import pl.edu.icm.unity.engine.api.authn.remote.RemotelyAuthenticatedInput;
24+
import pl.edu.icm.unity.engine.api.endpoint.SharedEndpointManagement;
25+
import pl.edu.icm.unity.engine.api.server.AdvertisedAddressProvider;
26+
import pl.edu.icm.unity.engine.api.utils.PrototypeComponent;
27+
import pl.edu.icm.unity.saml.SAMLResponseValidatorUtil;
28+
import pl.edu.icm.unity.types.translation.TranslationProfile;
29+
import xmlbeans.org.oasis.saml2.protocol.ResponseDocument;
30+
31+
@PrototypeComponent
32+
public class SAMLResponseVerificator
33+
{
34+
private static final Logger log = Log.getLogger(Log.U_SERVER_SAML, SAMLResponseVerificator.class);
35+
private final String responseConsumerAddress;
36+
private final ReplayAttackChecker replayAttackChecker;
37+
private final RemoteAuthnResultTranslator translator;
38+
39+
@Autowired
40+
public SAMLResponseVerificator(
41+
ReplayAttackChecker replayAttackChecker,
42+
SharedEndpointManagement sharedEndpointManagement,
43+
AdvertisedAddressProvider advertisedAddrProvider,
44+
RemoteAuthnResultTranslator translator)
45+
{
46+
this(replayAttackChecker,
47+
assembleResponseConsumerAddress(sharedEndpointManagement, advertisedAddrProvider),
48+
translator);
49+
}
50+
51+
public SAMLResponseVerificator(
52+
ReplayAttackChecker replayAttackChecker,
53+
String responseConsumerAddress,
54+
RemoteAuthnResultTranslator translator)
55+
{
56+
this.replayAttackChecker = replayAttackChecker;
57+
this.translator = translator;
58+
this.responseConsumerAddress = responseConsumerAddress;
59+
}
60+
61+
private static String assembleResponseConsumerAddress(SharedEndpointManagement sharedEndpointManagement,
62+
AdvertisedAddressProvider advertisedAddrProvider)
63+
{
64+
URL baseAddress = advertisedAddrProvider.get();
65+
String baseContext = sharedEndpointManagement.getBaseContextPath();
66+
return baseAddress + baseContext + SAMLResponseConsumerServlet.PATH;
67+
}
68+
69+
70+
AuthenticationResult processResponse(RedirectedAuthnState remoteAuthnState, TranslationProfile profile)
71+
{
72+
RemoteAuthnContext castedState = (RemoteAuthnContext) remoteAuthnState;
73+
try
74+
{
75+
return verifySAMLResponse(castedState, profile);
76+
} catch (Exception e)
77+
{
78+
log.error("Runtime error during SAML response processing or principal mapping", e);
79+
return RemoteAuthenticationResult.failed(null, e,
80+
new ResolvableError("WebSAMLRetrieval.authnFailedError"));
81+
}
82+
}
83+
84+
private AuthenticationResult verifySAMLResponse(RemoteAuthnContext context, TranslationProfile profile)
85+
{
86+
try
87+
{
88+
RemotelyAuthenticatedInput input = getRemotelyAuthenticatedInput(context);
89+
return translator.getTranslatedResult(input, profile,
90+
context.getAuthenticationTriggeringContext().isSandboxTriggered(),
91+
Optional.empty(),
92+
context.getRegistrationFormForUnknown(),
93+
context.isEnableAssociation());
94+
} catch (RemoteAuthenticationException e)
95+
{
96+
log.info("SAML response verification or processing failed", e);
97+
return RemoteAuthenticationResult.failed(e.getResult().getRemotelyAuthenticatedPrincipal(), e,
98+
new ResolvableError("WebSAMLRetrieval.authnFailedError"));
99+
}
100+
}
101+
102+
private RemotelyAuthenticatedInput getRemotelyAuthenticatedInput(RemoteAuthnContext context)
103+
throws RemoteAuthenticationException
104+
{
105+
ResponseDocument responseDocument;
106+
try
107+
{
108+
responseDocument = ResponseDocument.Factory.parse(context.getResponse());
109+
} catch (XmlException e)
110+
{
111+
throw new RemoteAuthenticationException("The SAML response can not be parsed - " +
112+
"XML data is corrupted", e);
113+
}
114+
115+
SAMLResponseValidatorUtil responseValidatorUtil = new SAMLResponseValidatorUtil(
116+
context.getContextConfig(), replayAttackChecker, responseConsumerAddress);
117+
118+
RemotelyAuthenticatedInput input = responseValidatorUtil.verifySAMLResponse(responseDocument,
119+
context.getVerifiableResponse(),
120+
context.getRequestId(),
121+
SAMLBindings.valueOf(context.getResponseBinding().toString()),
122+
context.getGroupAttribute(), context.getContextIdpKey());
123+
return input;
124+
}
125+
}
126+
127+

0 commit comments

Comments
 (0)