Skip to content
This repository was archived by the owner on Sep 28, 2022. It is now read-only.

Commit d75fc05

Browse files
author
Dominik František Bučík
authored
Merge pull request #233 from dBucik/dev_noatuhn_context
feat: return error response on noAuthnContext
2 parents e14ed41 + 7d1f731 commit d75fc05

File tree

12 files changed

+214
-56
lines changed

12 files changed

+214
-56
lines changed

perun-oidc-server-webapp/src/main/webapp/WEB-INF/web-context.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@
251251
access="permitAll()"/>
252252
<security:intercept-url pattern="#{T(cz.muni.ics.oidc.web.controllers.LoginController).MAPPING_FAILURE}"
253253
access="permitAll()"/>
254-
<security:intercept-url pattern="/**" access="hasRole('ROLE_USER')"/>
254+
<security:intercept-url pattern="/**" access="hasAnyRole('ROLE_USER','ROLE_EXCEPTION')"/>
255255
<security:custom-filter ref="mdcFilter" before="FIRST"/>
256256
<security:custom-filter ref="metadataGeneratorFilter" before="CHANNEL_FILTER"/>
257257
<security:custom-filter ref="clearSessionFilter" after="CHANNEL_FILTER"/>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package cz.muni.ics.oidc.saml;
2+
3+
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
4+
5+
public class ExtendedOAuth2Exception extends OAuth2Exception {
6+
7+
private final String errorCode;
8+
9+
public ExtendedOAuth2Exception(String errorCode, String msg) {
10+
super(msg);
11+
this.errorCode = errorCode;
12+
}
13+
14+
@Override
15+
public String getOAuth2ErrorCode() {
16+
return errorCode;
17+
}
18+
}

perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/PerunSamlAuthenticationProvider.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
import java.util.Collection;
66
import java.util.List;
77
import lombok.extern.slf4j.Slf4j;
8+
import org.springframework.security.core.Authentication;
9+
import org.springframework.security.core.AuthenticationException;
810
import org.springframework.security.core.GrantedAuthority;
911
import org.springframework.security.core.authority.SimpleGrantedAuthority;
10-
import org.springframework.security.core.userdetails.User;
1112
import org.springframework.security.saml.SAMLAuthenticationProvider;
1213
import org.springframework.security.saml.SAMLCredential;
1314

@@ -27,6 +28,15 @@ public PerunSamlAuthenticationProvider(List<String> adminIds) {
2728
}
2829
}
2930

31+
@Override
32+
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
33+
try {
34+
return super.authenticate(authentication);
35+
} catch (Exception e) {
36+
return new SamlAuthenticationExceptionAuthenticationToken(e);
37+
}
38+
}
39+
3040
@Override
3141
protected Object getPrincipal(SAMLCredential credential, Object userDetail) {
3242
PerunUser user = (PerunUser) userDetail;

perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/PerunSamlProcessingFilter.java

Lines changed: 0 additions & 48 deletions
This file was deleted.
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/* Copyright 2009 Vladimir Schäfer
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* https://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
package cz.muni.ics.oidc.saml;
16+
17+
import java.security.Principal;
18+
import java.util.Collections;
19+
import lombok.EqualsAndHashCode;
20+
import lombok.Getter;
21+
import lombok.ToString;
22+
import lombok.extern.slf4j.Slf4j;
23+
import org.opensaml.common.SAMLException;
24+
import org.opensaml.saml2.core.StatusCode;
25+
import org.springframework.security.authentication.AbstractAuthenticationToken;
26+
import org.springframework.security.authentication.InsufficientAuthenticationException;
27+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
28+
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
29+
import org.springframework.security.saml.SAMLStatusException;
30+
31+
@Getter
32+
@ToString
33+
@EqualsAndHashCode(callSuper = true)
34+
@Slf4j
35+
public class SamlAuthenticationExceptionAuthenticationToken extends AbstractAuthenticationToken {
36+
37+
private static final Principal PRINCIPAL = new SamlAuthenticationExceptionPrincipal();
38+
public static final SimpleGrantedAuthority ROLE_EXCEPTION = new SimpleGrantedAuthority("ROLE_EXCEPTION");
39+
private final Exception causeException;
40+
41+
public SamlAuthenticationExceptionAuthenticationToken(Exception causeException) {
42+
super(Collections.singleton(ROLE_EXCEPTION));
43+
this.causeException = causeException;
44+
}
45+
46+
@Override
47+
public Object getCredentials() {
48+
return "EXCEPTION_IN_SAML_AUTHENTICATION";
49+
}
50+
51+
@Override
52+
public Object getPrincipal() {
53+
return PRINCIPAL;
54+
}
55+
56+
@Override
57+
public boolean isAuthenticated() {
58+
return true;
59+
}
60+
61+
@Override
62+
public void eraseCredentials() { }
63+
64+
public OAuth2Exception createOAuth2Exception() {
65+
if (causeException != null) {
66+
Throwable t = causeException;
67+
while (t.getCause() != null) {
68+
log.warn("OAuth2 exception from SAML translation: {} - {}", t.getClass().getSimpleName(), t.getMessage());
69+
t = t.getCause();
70+
}
71+
if (t instanceof InsufficientAuthenticationException) {
72+
return new ExtendedOAuth2Exception("unmet_authentication_requirements", t.getMessage());
73+
}
74+
if (t instanceof SAMLStatusException) {
75+
String code = ((SAMLStatusException) t).getStatusCode();
76+
if (StatusCode.NO_AUTHN_CONTEXT_URI.equalsIgnoreCase(code)) {
77+
return new ExtendedOAuth2Exception("unmet_authentication_requirements", t.getMessage());
78+
}
79+
}
80+
return new OAuth2Exception(t.getMessage());
81+
}
82+
//TODO: handle
83+
return new OAuth2Exception("");
84+
}
85+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/* Copyright 2009 Vladimir Schäfer
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* https://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
package cz.muni.ics.oidc.saml;
16+
17+
import java.security.Principal;
18+
import java.util.Collections;
19+
import javax.security.auth.Subject;
20+
import lombok.EqualsAndHashCode;
21+
import lombok.Getter;
22+
import lombok.ToString;
23+
import org.springframework.security.authentication.AbstractAuthenticationToken;
24+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
25+
26+
public class SamlAuthenticationExceptionPrincipal implements Principal {
27+
28+
@Override
29+
public String getName() {
30+
return null;
31+
}
32+
33+
@Override
34+
public boolean implies(Subject subject) {
35+
return Principal.super.implies(subject);
36+
}
37+
}

perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/AuthProcFilter.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cz.muni.ics.oidc.server.filters;
22

33
import cz.muni.ics.oidc.exceptions.ConfigurationException;
4+
import cz.muni.ics.oidc.saml.SamlAuthenticationExceptionAuthenticationToken;
45
import cz.muni.ics.oidc.saml.SamlProperties;
56
import java.io.IOException;
67
import java.util.Arrays;
@@ -13,6 +14,8 @@
1314
import javax.servlet.http.HttpSession;
1415
import lombok.Getter;
1516
import lombok.extern.slf4j.Slf4j;
17+
import org.springframework.security.core.Authentication;
18+
import org.springframework.security.core.context.SecurityContextHolder;
1619

1720
/**
1821
* Abstract class for Perun AuthProc filters. All filters defined and called in the
@@ -117,6 +120,10 @@ private boolean skip(HttpServletRequest req) {
117120
}
118121
log.debug("{} - marking filter as applied", filterName);
119122
req.getSession(true).setAttribute(getSessionAppliedParamName(), true);
123+
Authentication a = SecurityContextHolder.getContext().getAuthentication();
124+
if (a instanceof SamlAuthenticationExceptionAuthenticationToken) {
125+
return true;
126+
}
120127
String sub = FiltersUtils.getUserIdentifier(req, samlProperties.getUserIdentifierAttribute());
121128
String clientId = FiltersUtils.getClientId(req);
122129

perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/FiltersUtils.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@ public static Map<String, String> createRequestMap(Map<String, String[]> paramet
9999
* @param clientService service fetching client details
100100
* @return extracted client, null if some error occurs
101101
*/
102-
@SuppressWarnings("unchecked")
103102
public static ClientDetailsEntity extractClientFromRequest(HttpServletRequest request,
104103
OAuth2RequestFactory authRequestFactory,
105104
ClientDetailsEntityService clientService)
@@ -199,11 +198,15 @@ public static PerunUser getPerunUserById(PerunAdapter perunAdapter, SAMLCredenti
199198
}
200199

201200
public static SAMLCredential getSamlCredential(HttpServletRequest request) {
202-
ExpiringUsernameAuthenticationToken p = (ExpiringUsernameAuthenticationToken) request.getUserPrincipal();
203-
if (p == null) {
201+
if (request.getUserPrincipal() instanceof ExpiringUsernameAuthenticationToken) {
202+
ExpiringUsernameAuthenticationToken p = (ExpiringUsernameAuthenticationToken) request.getUserPrincipal();
203+
if (p == null) {
204+
return null;
205+
}
206+
return (SAMLCredential) p.getCredentials();
207+
} else {
204208
return null;
205209
}
206-
return (SAMLCredential) p.getCredentials();
207210
}
208211

209212
public static String getExtLogin(SAMLCredential credential, String idAttribute) {

perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/LoginController.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22

33
import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
44
import cz.muni.ics.oidc.web.WebHtmlClasses;
5+
import cz.muni.ics.openid.connect.view.HttpCodeView;
6+
import cz.muni.ics.openid.connect.view.JsonErrorView;
57
import java.util.Map;
68
import javax.servlet.http.HttpServletRequest;
79
import lombok.extern.slf4j.Slf4j;
810
import org.opensaml.saml2.core.StatusCode;
911
import org.opensaml.ws.message.encoder.MessageEncodingException;
1012
import org.opensaml.xml.util.XMLHelper;
1113
import org.springframework.beans.factory.annotation.Autowired;
14+
import org.springframework.http.HttpStatus;
1215
import org.springframework.security.core.Authentication;
1316
import org.springframework.security.core.AuthenticationException;
1417
import org.springframework.security.core.context.SecurityContextHolder;
@@ -62,7 +65,11 @@ public String loginFailure(HttpServletRequest req, Map<String, Object> model) {
6265
if (exc != null) {
6366
String code = exc.getStatusCode();
6467
if (StatusCode.NO_AUTHN_CONTEXT_URI.equalsIgnoreCase(code)) {
65-
model.put(KEY_ERROR_MSG, "login_failure.no_authn_context.msg");
68+
model.put(HttpCodeView.CODE, HttpStatus.FORBIDDEN);
69+
model.put(JsonErrorView.ERROR, "unmet_authentication_requirements");
70+
model.put(JsonErrorView.ERROR_MESSAGE, "Cannot log in. MFA has been requested and not performed");
71+
return JsonErrorView.VIEWNAME;
72+
//model.put(KEY_ERROR_MSG, "login_failure.no_authn_context.msg");
6673
}
6774
}
6875
}

perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/request/ConnectOAuth2RequestFactory.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
package cz.muni.ics.openid.connect.request;
1919

2020

21+
import static cz.muni.ics.oidc.saml.SamlAuthenticationExceptionAuthenticationToken.ROLE_EXCEPTION;
22+
2123
import com.google.common.base.Strings;
2224
import com.google.gson.JsonElement;
2325
import com.google.gson.JsonObject;
@@ -37,17 +39,23 @@
3739
import cz.muni.ics.oauth2.model.ClientDetailsEntity;
3840
import cz.muni.ics.oauth2.model.PKCEAlgorithm;
3941
import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
42+
import cz.muni.ics.oidc.saml.SamlAuthenticationExceptionAuthenticationToken;
4043
import java.io.Serializable;
4144
import java.text.ParseException;
4245
import java.util.Collections;
4346
import java.util.Map;
4447
import java.util.Set;
4548
import lombok.extern.slf4j.Slf4j;
4649
import org.springframework.beans.factory.annotation.Autowired;
50+
import org.springframework.security.core.Authentication;
51+
import org.springframework.security.core.context.SecurityContextHolder;
4752
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
4853
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
4954
import org.springframework.security.oauth2.common.util.OAuth2Utils;
5055
import org.springframework.security.oauth2.provider.AuthorizationRequest;
56+
import org.springframework.security.oauth2.provider.ClientDetails;
57+
import org.springframework.security.oauth2.provider.OAuth2Request;
58+
import org.springframework.security.oauth2.provider.TokenRequest;
5159
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory;
5260
import org.springframework.stereotype.Component;
5361

@@ -151,6 +159,28 @@ public AuthorizationRequest createAuthorizationRequest(Map<String, String> input
151159
return request;
152160
}
153161

162+
@Override
163+
public OAuth2Request createOAuth2Request(AuthorizationRequest request) {
164+
if (request.getAuthorities() != null && request.getAuthorities().contains(ROLE_EXCEPTION)) {
165+
Authentication a = SecurityContextHolder.getContext().getAuthentication();
166+
if (a instanceof SamlAuthenticationExceptionAuthenticationToken) {
167+
throw ((SamlAuthenticationExceptionAuthenticationToken) a).createOAuth2Exception();
168+
}
169+
}
170+
return super.createOAuth2Request(request);
171+
}
172+
173+
@Override
174+
public TokenRequest createTokenRequest(AuthorizationRequest authorizationRequest, String grantType) {
175+
if (authorizationRequest.getAuthorities() != null && authorizationRequest.getAuthorities().contains(ROLE_EXCEPTION)) {
176+
Authentication a = SecurityContextHolder.getContext().getAuthentication();
177+
if (a instanceof SamlAuthenticationExceptionAuthenticationToken) {
178+
throw ((SamlAuthenticationExceptionAuthenticationToken) a).createOAuth2Exception();
179+
}
180+
}
181+
return super.createTokenRequest(authorizationRequest, grantType);
182+
}
183+
154184
/**
155185
*
156186
* @param jwtString

0 commit comments

Comments
 (0)