Skip to content
This repository was archived by the owner on Aug 10, 2021. It is now read-only.

Commit d2f540c

Browse files
committed
Merge branch 'develop'
2 parents 3267ac2 + e2e0052 commit d2f540c

File tree

12 files changed

+202
-34
lines changed

12 files changed

+202
-34
lines changed

src/main/java/ee/ria/sso/flow/action/AbstractAuthenticationAction.java

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@
77
import ee.ria.sso.flow.ThymeleafSupport;
88
import ee.ria.sso.service.ExternalServiceHasFailedException;
99
import ee.ria.sso.service.UserAuthenticationFailedException;
10+
import ee.ria.sso.service.manager.ManagerService;
1011
import lombok.AllArgsConstructor;
1112
import lombok.NoArgsConstructor;
1213
import lombok.extern.slf4j.Slf4j;
1314
import org.apereo.cas.authentication.principal.WebApplicationService;
15+
import org.apereo.cas.services.AbstractRegisteredService;
1416
import org.apereo.cas.support.oauth.authenticator.Authenticators;
17+
import org.apereo.cas.support.oauth.services.OAuthRegisteredService;
1518
import org.apereo.cas.web.support.WebUtils;
1619
import org.pac4j.core.context.Pac4jConstants;
1720
import org.springframework.beans.factory.annotation.Autowired;
@@ -22,6 +25,8 @@
2225

2326
import java.io.IOException;
2427
import java.security.cert.CertificateException;
28+
import java.util.List;
29+
import java.util.Optional;
2530

2631
import static ee.ria.sso.Constants.CAS_SERVICE_ATTRIBUTE_NAME;
2732

@@ -36,14 +41,19 @@ public abstract class AbstractAuthenticationAction extends AbstractAction {
3641
@Autowired
3742
private ThymeleafSupport thymeleafSupport;
3843

44+
@Autowired
45+
private ManagerService managerService;
46+
3947
protected abstract Event doAuthenticationExecute(RequestContext requestContext) throws IOException, CertificateException;
4048

4149
protected abstract AuthenticationType getAuthenticationType();
4250

4351
@Override
4452
protected Event doExecute(RequestContext requestContext) throws Exception {
4553

46-
assertSessionNotExpiredAndAuthMethodAllowed(requestContext);
54+
WebApplicationService service = getWebApplicationService(requestContext);
55+
assertValidClient(requestContext, service);
56+
assertSessionNotExpiredAndAuthMethodAllowed(requestContext, service);
4757

4858
try {
4959
return this.doAuthenticationExecute(requestContext);
@@ -65,19 +75,39 @@ protected Event doExecute(RequestContext requestContext) throws Exception {
6575
}
6676
}
6777

68-
private void assertSessionNotExpiredAndAuthMethodAllowed(RequestContext requestContext) {
69-
WebApplicationService service = getWebApplicationService(requestContext);
78+
private void assertValidClient(RequestContext requestContext, WebApplicationService service) {
7079
if (service == null) {
7180
log.error("Callback failed! No service parameter found in flow of session! Possible causes: either the user session has expired, server has been restarted in the middle of user transaction or corrupt/invalid cookie value was sent from the browser");
7281
throw AuthenticationFlowExecutionException.ofUnauthorized(requestContext, this, messageSource.getMessage(Constants.MESSAGE_KEY_SESSION_EXPIRED));
73-
} else if (isOauth2Client(service) && !requestContext.getExternalContext().getSessionMap().contains(Pac4jConstants.REQUESTED_URL)) {
74-
log.error("Oauth callback url not found in session! Possible causes: either the user session has expired, server has been restarted in the middle of user transaction or corrupt/invalid cookie value was sent from the browser");
75-
throw AuthenticationFlowExecutionException.ofUnauthorized(requestContext, this, messageSource.getMessage(Constants.MESSAGE_KEY_SESSION_EXPIRED));
7682
}
7783

78-
if (isOauth2Client(service) && !thymeleafSupport.isAuthMethodAllowed(getAuthenticationType())) {
79-
log.error("This authentication method usage was not initially specified by the scope parameter when the authentication process was initialized!");
80-
throw AuthenticationFlowExecutionException.ofUnauthorized(requestContext, this, messageSource.getMessage(Constants.MESSAGE_KEY_AUTH_METHOD_RESTRICTED_BY_SCOPE));
84+
if (!isOauth2Client(service) && !isCASClient(service.getOriginalUrl())) {
85+
log.error("Invalid service value in detected in webflow. Either the client_name parameter is invalid or the service URL is not allowed");
86+
throw AuthenticationFlowExecutionException.ofUnauthorized(requestContext, this, messageSource.getMessage(Constants.MESSAGE_KEY_GENERAL_ERROR));
87+
}
88+
}
89+
90+
private boolean isCASClient(String serviceUrl) {
91+
Optional<List<AbstractRegisteredService>> abstractRegisteredServices = managerService.getAllRegisteredServicesExceptType(OAuthRegisteredService.class);
92+
if (abstractRegisteredServices.isPresent()) {
93+
for (AbstractRegisteredService ars: abstractRegisteredServices.get()) {
94+
if (serviceUrl.matches(ars.getServiceId())) {
95+
return true;
96+
}
97+
}
98+
}
99+
return false;
100+
}
101+
102+
private void assertSessionNotExpiredAndAuthMethodAllowed(RequestContext requestContext, WebApplicationService service) {
103+
if (isOauth2Client(service)) {
104+
if (!requestContext.getExternalContext().getSessionMap().contains(Pac4jConstants.REQUESTED_URL)) {
105+
log.error("Oauth callback url not found in session! Possible causes: either the user session has expired, server has been restarted in the middle of user transaction or corrupt/invalid cookie value was sent from the browser");
106+
throw AuthenticationFlowExecutionException.ofUnauthorized(requestContext, this, messageSource.getMessage(Constants.MESSAGE_KEY_SESSION_EXPIRED));
107+
} else if (!thymeleafSupport.isAuthMethodAllowed(getAuthenticationType())) {
108+
log.error("This authentication method usage was not initially specified by the scope parameter when the authentication process was initialized!");
109+
throw AuthenticationFlowExecutionException.ofUnauthorized(requestContext, this, messageSource.getMessage(Constants.MESSAGE_KEY_AUTH_METHOD_RESTRICTED_BY_SCOPE));
110+
}
81111
}
82112
}
83113

src/main/java/ee/ria/sso/service/manager/ManagerService.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package ee.ria.sso.service.manager;
22

3+
import org.apereo.cas.services.AbstractRegisteredService;
34
import org.apereo.cas.services.OidcRegisteredService;
45
import org.apereo.cas.services.RegisteredServiceProperty;
56

7+
import java.util.List;
68
import java.util.Map;
79
import java.util.Optional;
810

@@ -15,5 +17,6 @@ public interface ManagerService {
1517
Optional<OidcRegisteredService> getServiceByName(String serviceName);
1618
Optional<Map<String, RegisteredServiceProperty>> getServiceNames(String serviceName);
1719
Optional<String> getServiceShortName();
20+
Optional<List<AbstractRegisteredService>> getAllRegisteredServicesExceptType(Class<?> type);
1821

1922
}

src/main/java/ee/ria/sso/service/manager/ManagerServiceImpl.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import ee.ria.sso.Constants;
44
import ee.ria.sso.utils.SessionMapUtil;
5+
import org.apereo.cas.config.CasOAuthConfiguration;
56
import org.apereo.cas.services.AbstractRegisteredService;
67
import org.apereo.cas.services.OidcRegisteredService;
78
import org.apereo.cas.services.RegisteredService;
@@ -28,9 +29,11 @@ public class ManagerServiceImpl implements ManagerService {
2829

2930
private final Logger log = LoggerFactory.getLogger(ManagerServiceImpl.class);
3031
private final ServicesManager servicesManager;
32+
private final CasOAuthConfiguration casOAuthConfiguration;
3133

32-
public ManagerServiceImpl(ServicesManager servicesManager) {
34+
public ManagerServiceImpl(ServicesManager servicesManager, CasOAuthConfiguration casOAuthConfiguration) {
3335
this.servicesManager = servicesManager;
36+
this.casOAuthConfiguration = casOAuthConfiguration;
3437
}
3538

3639
@Override
@@ -64,6 +67,15 @@ public Optional<Map<String, RegisteredServiceProperty>> getServiceNames(String s
6467
return service.map(AbstractRegisteredService::getProperties);
6568
}
6669

70+
71+
@Override
72+
public Optional<List<AbstractRegisteredService>> getAllRegisteredServicesExceptType(Class<?> type) {
73+
return Optional.of(this.servicesManager.getAllServices().stream()
74+
.filter(r -> r instanceof AbstractRegisteredService && !(type.isInstance(r)) && !(r.getServiceId().equals(getRegistryServiceURL())))
75+
.map(s -> (AbstractRegisteredService) s)
76+
.collect(Collectors.toList()));
77+
}
78+
6779
@Override
6880
public Optional<String> getServiceShortName() {
6981
String serviceName;
@@ -86,4 +98,8 @@ public Optional<String> getServiceShortName() {
8698

8799
return Optional.empty();
88100
}
101+
102+
private String getRegistryServiceURL() {
103+
return casOAuthConfiguration.oauthCallbackService().getId();
104+
}
89105
}

src/main/webapp/WEB-INF/classes/messages.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ message.idc.doesidcardexist=Do you have your ID-card inserted into the card read
8585
message.idc.nocertificate=No certificate found! Do you have your ID-card inserted into the card reader?
8686
message.idc.certnotyetvalid=Your certificates are invalid.
8787
message.idc.certexpired=Your certificates are invalid.
88-
message.idc.revoked=Your certificates are invalid.
88+
message.idc.revoked=Your certificates have been revoked. You can check the status of your ID card certificates in the DigiDoc4 client https://www.id.ee/en/article/validity-of-id-card-certificates-2/
8989
message.idc.unknown=Your certificates are invalid.
9090
message.idc.error.ocsp.not.available=Certificate status enquiry failed. Please try again later.
9191
message.idc.error=Certificate enquiry failed. Please try again later.

src/main/webapp/WEB-INF/classes/messages_et.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ message.idc.doesidcardexist=Kas Teie ID-kaart on kaardilugejasse sisestatud?
8585
message.idc.nocertificate=Sertifikaati ei leitud! Kas Teie ID-kaart on kaardilugejasse sisestatud?
8686
message.idc.certnotyetvalid=Teie sertifikaadid ei kehti.
8787
message.idc.certexpired=Teie sertifikaadid ei kehti.
88-
message.idc.revoked=Teie sertifikaadid ei kehti.
88+
message.idc.revoked=Teie sertifikaadid on tühistatud. Oma ID-kaardi sertifikaatide olekut saate kontrollida DigiDoc4 kliendis https://www.id.ee/artikkel/id-kaardi-sertifikaatide-kehtivus-2/
8989
message.idc.unknown=Teie sertifikaadid ei kehti.
9090
message.idc.error.ocsp.not.available=Sertifikaadi kehtivuse info küsimine ei õnnestunud. Palun proovige mõne aja pärast uuesti.
9191
message.idc.error=Sertifikaadi küsimine ei õnnestunud. Palun proovige mõne aja pärast uuesti.

src/main/webapp/WEB-INF/classes/messages_ru.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ message.idc.doesidcardexist=Пожалуйста проверьте, встав
8585
message.idc.nocertificate=Сертификат не найден! Пожалуйста проверьте, вставлена ли Ваша ID-карта в считыватель?
8686
message.idc.certnotyetvalid=Ваши сертификаты не действительны.
8787
message.idc.certexpired=Ваши сертификаты не действительны.
88-
message.idc.revoked=Ваши сертификаты не действительны.
88+
message.idc.revoked=Ваши сертификаты аннулированы. Вы можете проверить статус сертификатов вашей ID-карты в клиенте DigiDoc4 https://www.id.ee/ru/artikkel/dejstvitelnost-sertifikatov-id-karty-2/
8989
message.idc.unknown=Ваши сертификаты не действительны.
9090
message.idc.error.ocsp.not.available=Запрос сертификата не удался. Пожалуйста, попробуйте позже.
9191
message.idc.error=Запрос сертификата не удался. Пожалуйста, попробуйте позже.

src/test/java/ee/ria/sso/config/TestTaraConfiguration.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
import org.apereo.cas.audit.AuditTrailRecordResolutionPlan;
88
import org.apereo.cas.audit.AuditableExecution;
99
import org.apereo.cas.audit.spi.DefaultAuditTrailRecordResolutionPlan;
10+
import org.apereo.cas.authentication.AuthenticationSystemSupport;
1011
import org.apereo.cas.authentication.principal.PrincipalFactory;
1112
import org.apereo.cas.authentication.principal.ServiceFactory;
1213
import org.apereo.cas.authentication.principal.WebApplicationService;
14+
import org.apereo.cas.config.CasOAuthConfiguration;
1315
import org.apereo.cas.oidc.token.OidcIdTokenSigningAndEncryptionService;
1416
import org.apereo.cas.oidc.util.OidcAuthorizationRequestSupport;
1517
import org.apereo.cas.services.OidcRegisteredService;
@@ -103,6 +105,18 @@ public ServicesManager servicesManager() {
103105
return Mockito.mock(ServicesManager.class);
104106
}
105107

108+
@Bean
109+
@Qualifier("casOAuthConfiguration")
110+
public CasOAuthConfiguration casOAuthConfiguration() {
111+
return Mockito.mock(CasOAuthConfiguration.class);
112+
}
113+
114+
@Bean
115+
@Qualifier("defaultAuthenticationSystemSupport")
116+
public AuthenticationSystemSupport authenticationSystemSupport() {
117+
return Mockito.mock(AuthenticationSystemSupport.class);
118+
}
119+
106120
@Bean
107121
@Qualifier("oidcPrincipalFactory")
108122
public PrincipalFactory oidcPrincipalFactory() {return Mockito.mock(PrincipalFactory.class); }

src/test/java/ee/ria/sso/flow/action/AbstractAuthenticationActionTest.java

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22

33
import ee.ria.sso.AbstractTest;
44
import ee.ria.sso.Constants;
5-
import ee.ria.sso.service.ExternalServiceHasFailedException;
6-
import ee.ria.sso.service.UserAuthenticationFailedException;
75
import ee.ria.sso.authentication.AuthenticationType;
86
import ee.ria.sso.config.TaraResourceBundleMessageSource;
97
import ee.ria.sso.flow.AuthenticationFlowExecutionException;
108
import ee.ria.sso.flow.ThymeleafSupport;
9+
import ee.ria.sso.service.ExternalServiceHasFailedException;
10+
import ee.ria.sso.service.UserAuthenticationFailedException;
11+
import ee.ria.sso.service.manager.ManagerService;
1112
import org.apereo.cas.authentication.principal.AbstractWebApplicationService;
13+
import org.apereo.cas.services.AbstractRegisteredService;
14+
import org.apereo.cas.support.oauth.services.OAuthRegisteredService;
1215
import org.hamcrest.Description;
1316
import org.hamcrest.TypeSafeMatcher;
1417
import org.junit.Before;
@@ -21,7 +24,10 @@
2124
import org.springframework.webflow.execution.Event;
2225
import org.springframework.webflow.execution.RequestContext;
2326

27+
import java.util.ArrayList;
2428
import java.util.Collections;
29+
import java.util.List;
30+
import java.util.Optional;
2531

2632
import static org.junit.Assert.assertTrue;
2733

@@ -33,6 +39,9 @@ public abstract class AbstractAuthenticationActionTest {
3339
@Mock
3440
private TaraResourceBundleMessageSource messageSource;
3541

42+
@Mock
43+
private ManagerService managerService;
44+
3645
@Rule
3746
public ExpectedException expectedEx = ExpectedException.none();
3847

@@ -47,6 +56,8 @@ public void setUp() {
4756
requestContext.getExternalContext().getSessionMap().put(Pac4jConstants.REQUESTED_URL, "https://localhost:8451/response");
4857
requestContext.getExternalContext().getSessionMap().put(Constants.TARA_OIDC_SESSION_AUTH_METHODS, Collections.singletonList(AuthenticationType.SmartID));
4958
Mockito.when(thymeleafSupport.isAuthMethodAllowed(Mockito.any())).thenReturn(true);
59+
Optional<List<AbstractRegisteredService>> mockedAbstractRegisteredServices = mockAbstractRegisteredServices();
60+
Mockito.when(managerService.getAllRegisteredServicesExceptType(OAuthRegisteredService.class)).thenReturn(mockedAbstractRegisteredServices);
5061
}
5162

5263
@Test
@@ -64,6 +75,12 @@ public void successWhenValidServiceMissingFromFlowContextAndPresentInSession() t
6475
getAction().doExecute(requestContext);
6576
}
6677

78+
@Test
79+
public void successWhenValidAbstractServicePresentButNoMatchingServiceURL() throws Exception {
80+
requestContext.getFlowScope().put(Constants.CAS_SERVICE_ATTRIBUTE_NAME, new AbstractWebApplicationService("id", "https://not-cas.server.url/?client_name=CasOAuthClient", "artifactId") {});
81+
getAction().doExecute(requestContext);
82+
}
83+
6784
@Test
6885
public void exceptionWhenAuthenticationMethodNotInAllowedList() throws Exception {
6986
expectedEx.expect(AuthenticationFlowExecutionException.class);
@@ -77,7 +94,8 @@ public void exceptionWhenAuthenticationMethodNotInAllowedList() throws Exception
7794

7895
@Test
7996
public void invalidOriginalUrlInService() throws Exception {
80-
requestContext.getFlowScope().put(Constants.CAS_SERVICE_ATTRIBUTE_NAME, new AbstractWebApplicationService("id", null, "artifactId") {});
97+
expectedEx.expect(AuthenticationFlowExecutionException.class);
98+
requestContext.getFlowScope().put(Constants.CAS_SERVICE_ATTRIBUTE_NAME, new AbstractWebApplicationService("id", "", "artifactId") {});
8199
getAction().doExecute(requestContext);
82100
}
83101

@@ -89,7 +107,7 @@ public void unexpectedExceptionOccursDuringAuthentication() throws Exception {
89107
try {
90108

91109
Mockito.when(messageSource.getMessage(Mockito.eq(Constants.MESSAGE_KEY_GENERAL_ERROR))).thenReturn("mock general error");
92-
new AbstractAuthenticationAction(messageSource, thymeleafSupport) {
110+
new AbstractAuthenticationAction(messageSource, thymeleafSupport, managerService) {
93111

94112
@Override
95113
protected Event doAuthenticationExecute(RequestContext requestContext) {
@@ -115,7 +133,7 @@ public void upstreamServiceExceptionOccursDuringAuthentication() throws Exceptio
115133

116134
try {
117135
Mockito.when(messageSource.getMessage(Mockito.eq("msg.key"))).thenReturn("mock translation");
118-
new AbstractAuthenticationAction(messageSource, thymeleafSupport) {
136+
new AbstractAuthenticationAction(messageSource, thymeleafSupport, managerService) {
119137

120138
@Override
121139
protected Event doAuthenticationExecute(RequestContext requestContext) {
@@ -141,7 +159,7 @@ public void authFailedExceptionOccursDuringAuthentication() throws Exception {
141159

142160
try {
143161
Mockito.when(messageSource.getMessage(Mockito.eq("msg.key"))).thenReturn("Mock translation");
144-
new AbstractAuthenticationAction(messageSource, thymeleafSupport) {
162+
new AbstractAuthenticationAction(messageSource, thymeleafSupport, managerService) {
145163

146164
@Override
147165
protected Event doAuthenticationExecute(RequestContext requestContext) {
@@ -185,6 +203,16 @@ public void errorWhenValidServicePresentAndUsinCasOauthClientIsMissingCallbackUr
185203
getAction().doExecute(requestContext);
186204
}
187205

206+
@Test
207+
public void exceptionWhenClientNameIsInvalid() throws Exception {
208+
Mockito.when(messageSource.getMessage(Constants.MESSAGE_KEY_GENERAL_ERROR)).thenReturn("Mock general error");
209+
210+
expectedEx.expect(AuthenticationFlowExecutionException.class);
211+
expectedEx.expect(new ExceptionCodeMatches(401, "Mock general error"));
212+
requestContext.getFlowScope().put(Constants.CAS_SERVICE_ATTRIBUTE_NAME, new AbstractWebApplicationService("id", "", "artifactId") {});
213+
getAction().doExecute(requestContext);
214+
}
215+
188216

189217
class ExceptionCodeMatches extends TypeSafeMatcher<AuthenticationFlowExecutionException> {
190218
private int code;
@@ -219,4 +247,13 @@ protected void describeMismatchSafely(AuthenticationFlowExecutionException item,
219247
private void assertContextCleared(RequestContext requestContext) {
220248
assertTrue("flow context was not cleared!", requestContext.getFlowScope().isEmpty());
221249
}
250+
251+
private Optional<List<AbstractRegisteredService>> mockAbstractRegisteredServices() {
252+
List<AbstractRegisteredService> abstractRegisteredServices = new ArrayList<>();
253+
AbstractRegisteredService abstractRegisteredService = Mockito.mock(AbstractRegisteredService.class);
254+
Mockito.when(abstractRegisteredService.getServiceId()).thenReturn("^https://cas.server.url.*");
255+
256+
abstractRegisteredServices.add(abstractRegisteredService);
257+
return Optional.of(abstractRegisteredServices);
258+
}
222259
}

0 commit comments

Comments
 (0)