refactor: 💡 refactored device code flow and filters

pull/1580/head
Dominik Frantisek Bucik 2022-01-05 15:28:16 +01:00
parent 22527c9996
commit e8b34f9079
No known key found for this signature in database
GPG Key ID: 25014C8DB2E7E62D
22 changed files with 262 additions and 246 deletions

View File

@ -65,7 +65,9 @@
<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.ProtectedResourceRegistrationEndpoint).URL}/**" /> <mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.ProtectedResourceRegistrationEndpoint).URL}/**" />
<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.UserInfoEndpoint).URL}**" /> <mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.UserInfoEndpoint).URL}**" />
<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.RootController).API_URL}/**" /> <mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.RootController).API_URL}/**" />
<mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.DeviceEndpoint).URL}/**" /> <mvc:exclude-mapping path="#{T(cz.muni.ics.oauth2.web.DeviceEndpoint).ENDPOINT_URL}**" />
<mvc:exclude-mapping path="#{T(cz.muni.ics.oauth2.web.DeviceEndpoint).REQUEST_USER_CODE_URL}**" />
<mvc:exclude-mapping path="#{T(cz.muni.ics.oauth2.web.DeviceEndpoint).DEVICE_APPROVED_URL}**" />
<mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.IntrospectionEndpoint).URL}**" /> <mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.IntrospectionEndpoint).URL}**" />
<mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.RevocationEndpoint).URL}**" /> <mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.RevocationEndpoint).URL}**" />
@ -83,7 +85,7 @@
<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.ProtectedResourceRegistrationEndpoint).URL}/**" /> <mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.ProtectedResourceRegistrationEndpoint).URL}/**" />
<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.UserInfoEndpoint).URL}**" /> <mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.UserInfoEndpoint).URL}**" />
<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.RootController).API_URL}/**" /> <mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.RootController).API_URL}/**" />
<mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.DeviceEndpoint).URL}/**" /> <mvc:exclude-mapping path="#{T(cz.muni.ics.oauth2.web.DeviceEndpoint).ENDPOINT_URL}**" />
<mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.IntrospectionEndpoint).URL}**" /> <mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.IntrospectionEndpoint).URL}**" />
<mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.RevocationEndpoint).URL}**" /> <mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.RevocationEndpoint).URL}**" />
<!-- Inject the server configuration into the response --> <!-- Inject the server configuration into the response -->
@ -189,7 +191,7 @@
<security:csrf disabled="true"/> <security:csrf disabled="true"/>
</security:http> </security:http>
<security:http pattern="/#{T(cz.muni.ics.oauth2.web.DeviceEndpoint).URL}/**" <security:http pattern="#{T(cz.muni.ics.oauth2.web.DeviceEndpoint).ENDPOINT_URL}**"
use-expressions="true" use-expressions="true"
entry-point-ref="oauthAuthenticationEntryPoint" entry-point-ref="oauthAuthenticationEntryPoint"
create-session="stateless" create-session="stateless"

View File

@ -47,7 +47,9 @@
<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.ProtectedResourceRegistrationEndpoint).URL}/**" /> <mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.ProtectedResourceRegistrationEndpoint).URL}/**" />
<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.UserInfoEndpoint).URL}**" /> <mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.UserInfoEndpoint).URL}**" />
<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.RootController).API_URL}/**" /> <mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.RootController).API_URL}/**" />
<mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.DeviceEndpoint).URL}/**" /> <mvc:exclude-mapping path="#{T(cz.muni.ics.oauth2.web.DeviceEndpoint).ENDPOINT_URL}**" />
<mvc:exclude-mapping path="#{T(cz.muni.ics.oauth2.web.DeviceEndpoint).REQUEST_USER_CODE_URL}**" />
<mvc:exclude-mapping path="#{T(cz.muni.ics.oauth2.web.DeviceEndpoint).DEVICE_APPROVED_URL}**" />
<mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.IntrospectionEndpoint).URL}**" /> <mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.IntrospectionEndpoint).URL}**" />
<mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.RevocationEndpoint).URL}**" /> <mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.RevocationEndpoint).URL}**" />
<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.IsTestSpController).MAPPING}**" /> <mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.IsTestSpController).MAPPING}**" />
@ -82,7 +84,7 @@
<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.ProtectedResourceRegistrationEndpoint).URL}/**" /> <mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.ProtectedResourceRegistrationEndpoint).URL}/**" />
<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.UserInfoEndpoint).URL}**" /> <mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.UserInfoEndpoint).URL}**" />
<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.RootController).API_URL}/**" /> <mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.RootController).API_URL}/**" />
<mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.DeviceEndpoint).URL}/**" /> <mvc:exclude-mapping path="#{T(cz.muni.ics.oauth2.web.DeviceEndpoint).ENDPOINT_URL}**" />
<mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.IntrospectionEndpoint).URL}**" /> <mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.IntrospectionEndpoint).URL}**" />
<mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.RevocationEndpoint).URL}**" /> <mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.RevocationEndpoint).URL}**" />
<!-- Inject the server configuration into the response --> <!-- Inject the server configuration into the response -->
@ -495,13 +497,13 @@
<!-- SAML --> <!-- SAML -->
<bean id="clearSessionFilter" class="cz.muni.ics.oidc.saml.SamlInvalidateSessionFilter"> <bean id="clearSessionFilter" class="cz.muni.ics.oidc.saml.SamlInvalidateSessionFilter">
<constructor-arg name="pattern" value="/authorize**"/>
<constructor-arg name="oidcIssuer" value="${main.oidc.issuer.url}"/> <constructor-arg name="oidcIssuer" value="${main.oidc.issuer.url}"/>
<constructor-arg name="idpEntityId" value="${saml.idp.defaultIdpEntityId}"/> <constructor-arg name="idpEntityId" value="${saml.idp.defaultIdpEntityId}"/>
<constructor-arg name="proxySpEntityId" value="${saml.proxy.spEntityId}"/> <constructor-arg name="proxySpEntityId" value="${saml.proxy.spEntityId}"/>
<constructor-arg name="internalReferrers" value="#{'${saml.internalReferrers}'.split('\s*,\s*')}"/> <constructor-arg name="internalReferrers" value="#{'${saml.internalReferrers}'.split('\s*,\s*')}"/>
<constructor-arg name="contextLogoutHandler" ref="logoutHandler"/> <constructor-arg name="contextLogoutHandler" ref="logoutHandler"/>
</bean> </bean>
<bean id="samlDiscovery" class="org.springframework.security.saml.SAMLDiscovery"> <bean id="samlDiscovery" class="org.springframework.security.saml.SAMLDiscovery">
<property name="contextProvider" ref="samlContextProvider"/> <property name="contextProvider" ref="samlContextProvider"/>
<property name="samlEntryPoint" ref="samlEntryPoint"/> <property name="samlEntryPoint" ref="samlEntryPoint"/>

View File

@ -37,7 +37,7 @@
</h1> </h1>
<form name="confirmationForm" <form name="confirmationForm"
action="${ config.issuer }${ config.issuer.endsWith('/') ? '' : '/' }device/approve" method="post"> action="${ config.issuer }${ config.issuer.endsWith('/') ? '' : '/' }device/approved" method="post">
<div class="row"> <div class="row">
<div class="span5 offset1 well-small" style="text-align: left"> <div class="span5 offset1 well-small" style="text-align: left">

View File

@ -39,14 +39,15 @@
</c:if> </c:if>
<form action="${ config.issuer }${ config.issuer.endsWith('/') ? '' : '/' }device/verify" method="POST"> <form action="${ config.issuer }${ config.issuer.endsWith('/') ? '' : '/' }device/code" method="POST">
<div class="row-fluid"> <div class="row-fluid">
<div class="span12"> <div class="span12">
<spring:message code="device.request_code.submit" var="authorize_label"/> <spring:message code="device.request_code.submit" var="authorize_label"/>
<div> <div>
<div class="input-block-level input-xlarge"> <div class="input-block-level input-xlarge">
<input type="text" name="user_code" placeholder="code" autocorrect="off" autocapitalize="off" autocomplete="off" spellcheck="false" value="" /> <input type="text" name="user_code" placeholder="code" autocorrect="off"
autocapitalize="off" autocomplete="off" spellcheck="false" value="${user_code}" />
</div> </div>
</div> </div>
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" /> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />

View File

@ -33,7 +33,7 @@
<div id="content"> <div id="content">
<c:remove scope="session" var="SPRING_SECURITY_LAST_EXCEPTION" /> <c:remove scope="session" var="SPRING_SECURITY_LAST_EXCEPTION" />
<form name="confirmationForm" <form name="confirmationForm"
action="${ config.issuer }${ config.issuer.endsWith('/') ? '' : '/' }device/approve" method="post"> action="${ config.issuer }${ config.issuer.endsWith('/') ? '' : '/' }device/approved" method="post">
<p> <p>
<c:if test="${not empty client.policyUri}"> <c:if test="${not empty client.policyUri}">
<spring:message code="device_approve_privacy"/>${" "}<a target='_blank' href='${fn:escapeXml(client.policyUri)}'><em>${fn:escapeXml(client.clientName)}</em></a> <spring:message code="device_approve_privacy"/>${" "}<a target='_blank' href='${fn:escapeXml(client.policyUri)}'><em>${fn:escapeXml(client.clientName)}</em></a>

View File

@ -43,7 +43,7 @@ pageContext.setAttribute("cssLinks", cssLinks);
</c:if> </c:if>
${". "}<spring:message code="device_approved_text_rejected_end"/> ${". "}<spring:message code="device_approved_text_rejected_end"/>
</c:if> </c:if>
</p>q </p>
</div> </div>
</div> <%-- wrap --%> </div> <%-- wrap --%>

View File

@ -51,15 +51,15 @@
</c:otherwise> </c:otherwise>
</c:choose> </c:choose>
<form name="confirmationForm" class="mt-2" method="post" <form name="confirmationForm" class="mt-2" method="POST"
action="${ config.issuer }${ config.issuer.endsWith('/') ? '' : '/' }device/verify"> action="${ config.issuer }${ config.issuer.endsWith('/') ? '' : '/' }device/code">
<div class="row-fluid"> <div class="row-fluid">
<div class="span12"> <div class="span12">
<div> <div>
<div class="input-block-level input-xlarge"> <div class="input-block-level input-xlarge">
<spring:message code="code" var="code_placeholder"/> <spring:message code="code" var="code_placeholder"/>
<input type="text" name="user_code" placeholder="${code_placeholder}" <input type="text" name="user_code" placeholder="${code_placeholder}"
autocapitalize="off" autocomplete="off" spellcheck="false" value="" /> autocapitalize="off" autocomplete="off" spellcheck="false" value="${user_code}" />
</div> </div>
</div> </div>
</div> </div>

View File

@ -346,7 +346,7 @@ public class DiscoveryEndpoint {
m.put("code_challenge_methods_supported", Lists.newArrayList(PKCEAlgorithm.plain.getName(), PKCEAlgorithm.S256.getName())); m.put("code_challenge_methods_supported", Lists.newArrayList(PKCEAlgorithm.plain.getName(), PKCEAlgorithm.S256.getName()));
m.put("device_authorization_endpoint", baseUrl + DeviceEndpoint.URL); m.put("device_authorization_endpoint", config.getIssuer(false) + DeviceEndpoint.ENDPOINT_URL);
model.addAttribute(JsonEntityView.ENTITY, m); model.addAttribute(JsonEntityView.ENTITY, m);

View File

@ -16,34 +16,29 @@
package cz.muni.ics.oauth2.web; package cz.muni.ics.oauth2.web;
import com.google.common.collect.Sets;
import cz.muni.ics.oauth2.exception.DeviceCodeCreationException; import cz.muni.ics.oauth2.exception.DeviceCodeCreationException;
import cz.muni.ics.oauth2.model.ClientDetailsEntity; import cz.muni.ics.oauth2.model.ClientDetailsEntity;
import cz.muni.ics.oauth2.model.ClientWithScopes; import cz.muni.ics.oauth2.model.ClientWithScopes;
import cz.muni.ics.oauth2.model.DeviceCode; import cz.muni.ics.oauth2.model.DeviceCode;
import cz.muni.ics.oauth2.model.SystemScope;
import cz.muni.ics.oauth2.service.ClientDetailsEntityService; import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
import cz.muni.ics.oauth2.service.DeviceCodeService; import cz.muni.ics.oauth2.service.DeviceCodeService;
import cz.muni.ics.oauth2.service.SystemScopeService; import cz.muni.ics.oauth2.service.SystemScopeService;
import cz.muni.ics.oauth2.token.DeviceTokenGranter; import cz.muni.ics.oauth2.token.DeviceTokenGranter;
import cz.muni.ics.oidc.server.PerunScopeClaimTranslationService;
import cz.muni.ics.oidc.server.configurations.PerunOidcConfig; import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
import cz.muni.ics.oidc.server.userInfo.PerunUserInfo; import cz.muni.ics.oidc.server.userInfo.PerunUserInfo;
import cz.muni.ics.oidc.web.WebHtmlClasses; import cz.muni.ics.oidc.web.WebHtmlClasses;
import cz.muni.ics.oidc.web.controllers.ControllerUtils; import cz.muni.ics.oidc.web.controllers.ControllerUtils;
import cz.muni.ics.openid.connect.service.ScopeClaimTranslationService;
import cz.muni.ics.openid.connect.service.UserInfoService; import cz.muni.ics.openid.connect.service.UserInfoService;
import cz.muni.ics.openid.connect.view.HttpCodeView; import cz.muni.ics.openid.connect.view.HttpCodeView;
import cz.muni.ics.openid.connect.view.JsonEntityView; import cz.muni.ics.openid.connect.view.JsonEntityView;
import cz.muni.ics.openid.connect.view.JsonErrorView; import cz.muni.ics.openid.connect.view.JsonErrorView;
import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.security.Principal; import java.security.Principal;
import java.util.Collection; import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
@ -64,10 +59,10 @@ import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap; import org.springframework.ui.ModelMap;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
/** /**
* Implements https://tools.ietf.org/html/draft-ietf-oauth-device-flow * Implements https://tools.ietf.org/html/draft-ietf-oauth-device-flow
@ -87,6 +82,7 @@ public class DeviceEndpoint {
public static final String APPROVE_DEVICE = "approveDevice"; public static final String APPROVE_DEVICE = "approveDevice";
public static final String THEMED_APPROVE_DEVICE = "themedApproveDevice"; public static final String THEMED_APPROVE_DEVICE = "themedApproveDevice";
public static final String DEVICE_APPROVED = "deviceApproved"; public static final String DEVICE_APPROVED = "deviceApproved";
public static final String THEMED_DEVICE_APPROVED = "themedDeviceApproved";
// response keys // response keys
public static final String DEVICE_CODE = "device_code"; public static final String DEVICE_CODE = "device_code";
@ -107,7 +103,6 @@ public class DeviceEndpoint {
public static final String APPROVED = "approved"; public static final String APPROVED = "approved";
public static final String SCOPES = "scopes"; public static final String SCOPES = "scopes";
public static final String PAGE = "page"; public static final String PAGE = "page";
public static final String ACR = "acr";
public static final String ERROR = "error"; public static final String ERROR = "error";
// session attributes // session attributes
@ -123,8 +118,10 @@ public class DeviceEndpoint {
// other // other
public static final String DEFAULT = "default"; public static final String DEFAULT = "default";
public static final String URL = "devicecode"; public static final String ENDPOINT_URL = "/devicecode";
public static final String USER_URL = "device"; public static final String REQUEST_USER_CODE_URL = "/device/code";
public static final String CHECK_USER_CODE_URL = "/device/checkcode";
public static final String DEVICE_APPROVED_URL = "/device/approved";
private final ClientDetailsEntityService clientService; private final ClientDetailsEntityService clientService;
private final SystemScopeService scopeService; private final SystemScopeService scopeService;
@ -132,7 +129,7 @@ public class DeviceEndpoint {
private final OAuth2RequestFactory oAuth2RequestFactory; private final OAuth2RequestFactory oAuth2RequestFactory;
private final PerunOidcConfig perunOidcConfig; private final PerunOidcConfig perunOidcConfig;
private final WebHtmlClasses htmlClasses; private final WebHtmlClasses htmlClasses;
private final PerunScopeClaimTranslationService scopeClaimTranslationService; private final ScopeClaimTranslationService scopeClaimTranslationService;
private final UserInfoService userInfoService; private final UserInfoService userInfoService;
@Autowired @Autowired
@ -142,9 +139,9 @@ public class DeviceEndpoint {
OAuth2RequestFactory oAuth2RequestFactory, OAuth2RequestFactory oAuth2RequestFactory,
PerunOidcConfig perunOidcConfig, PerunOidcConfig perunOidcConfig,
WebHtmlClasses htmlClasses, WebHtmlClasses htmlClasses,
PerunScopeClaimTranslationService scopeClaimTranslationService, ScopeClaimTranslationService scopeClaimTranslationService,
UserInfoService userInfoService) { UserInfoService userInfoService)
{
this.clientService = clientService; this.clientService = clientService;
this.scopeService = scopeService; this.scopeService = scopeService;
this.deviceCodeService = deviceCodeService; this.deviceCodeService = deviceCodeService;
@ -155,20 +152,16 @@ public class DeviceEndpoint {
this.userInfoService = userInfoService; this.userInfoService = userInfoService;
} }
@RequestMapping( @PostMapping(value = ENDPOINT_URL, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
value = "/" + URL, produces = MediaType.APPLICATION_JSON_VALUE)
method = RequestMethod.POST,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE
)
public String requestDeviceCode(@RequestParam(CLIENT_ID) String clientId, public String requestDeviceCode(@RequestParam(CLIENT_ID) String clientId,
@RequestParam(name = SCOPE, required = false) String scope, @RequestParam(name = SCOPE, required = false) String scope,
@RequestParam(name = ACR_VALUES, required = false) String acrValues, @RequestParam(name = ACR_VALUES, required = false) String acrValues,
Map<String, String> parameters, Map<String, String> parameters,
ModelMap model) { ModelMap model)
{
ClientWithScopes clientWithScopes = new ClientWithScopes(); ClientWithScopes clientWithScopes = new ClientWithScopes();
String errorViewName = handleRequest(clientId, clientWithScopes, scope, model); String errorViewName = preprocessRequest(clientId, clientWithScopes, scope, model);
if (errorViewName != null) { if (errorViewName != null) {
return errorViewName; return errorViewName;
@ -186,78 +179,56 @@ public class DeviceEndpoint {
response.put(DEVICE_CODE, dc.getDeviceCode()); response.put(DEVICE_CODE, dc.getDeviceCode());
response.put(USER_CODE, dc.getUserCode()); response.put(USER_CODE, dc.getUserCode());
Map<String, String> uriParams = new HashMap<>();
if (StringUtils.hasText(acrValues)) { if (StringUtils.hasText(acrValues)) {
response.put( uriParams.put(ACR_VALUES, acrValues);
VERIFICATION_URI, }
constructURI(perunOidcConfig.getConfigBean().getIssuer() + USER_URL, Map.of(ACR_VALUES, acrValues)) String uriBase = perunOidcConfig.getConfigBean().getIssuer(false) + REQUEST_USER_CODE_URL;
); response.put(VERIFICATION_URI, constructVerificationURI(uriBase, uriParams));
} else {
response.put(VERIFICATION_URI, perunOidcConfig.getConfigBean().getIssuer() + USER_URL); if (perunOidcConfig.getConfigBean().isAllowCompleteDeviceCodeUri()) {
uriParams.put(USER_CODE, dc.getUserCode());
response.put(VERIFICATION_URI_COMPLETE, constructVerificationURI(uriBase, uriParams));
} }
if (clientWithScopes.getClient().getDeviceCodeValiditySeconds() != null) { if (clientWithScopes.getClient().getDeviceCodeValiditySeconds() != null) {
response.put(EXPIRES_IN, clientWithScopes.getClient().getDeviceCodeValiditySeconds()); response.put(EXPIRES_IN, clientWithScopes.getClient().getDeviceCodeValiditySeconds());
} }
if (perunOidcConfig.getConfigBean().isAllowCompleteDeviceCodeUri()) {
URI verificationUriComplete = new URIBuilder(perunOidcConfig.getConfigBean().getIssuer() + USER_URL)
.addParameter(USER_CODE, dc.getUserCode())
.build();
response.put(
VERIFICATION_URI_COMPLETE,
constructURI(
perunOidcConfig.getConfigBean().getIssuer() + USER_URL,
Map.of(ACR_VALUES, acrValues, USER_CODE, dc.getUserCode())
)
);
}
model.put(JsonEntityView.ENTITY, response); model.put(JsonEntityView.ENTITY, response);
return JsonEntityView.VIEWNAME; return JsonEntityView.VIEWNAME;
} catch (DeviceCodeCreationException dcce) { } catch (DeviceCodeCreationException ex) {
model.put(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); model.put(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
model.put(JsonErrorView.ERROR, dcce.getError()); model.put(JsonErrorView.ERROR, ex.getError());
model.put(JsonErrorView.ERROR_MESSAGE, dcce.getMessage()); model.put(JsonErrorView.ERROR_MESSAGE, ex.getMessage());
return JsonErrorView.VIEWNAME; return JsonErrorView.VIEWNAME;
} catch (URISyntaxException use) { } catch (URISyntaxException ex) {
log.error("unable to build verification_uri_complete due to wrong syntax of uri components"); log.error("unable to build verification_uri_complete due to wrong syntax of uri components");
model.put(HttpCodeView.CODE, HttpStatus.INTERNAL_SERVER_ERROR); model.put(HttpCodeView.CODE, HttpStatus.INTERNAL_SERVER_ERROR);
return HttpCodeView.VIEWNAME; return HttpCodeView.VIEWNAME;
} }
} }
@PreAuthorize("hasRole('ROLE_USER')") @PreAuthorize("hasRole('ROLE_USER')")
@RequestMapping(value = "/" + USER_URL, method = RequestMethod.GET) @GetMapping(value = REQUEST_USER_CODE_URL)
public String requestUserCode(@RequestParam(value = USER_CODE, required = false) String userCode, public String requestUserCode(@RequestParam(value = USER_CODE, required = false) String userCode,
@ModelAttribute(AUTHORIZATION_REQUEST) AuthorizationRequest authRequest,
Principal p,
HttpServletRequest req, HttpServletRequest req,
ModelMap model, ModelMap model)
HttpSession session) { {
if (perunOidcConfig.getConfigBean().isAllowCompleteDeviceCodeUri() && StringUtils.hasText(userCode)) {
if (!perunOidcConfig.getConfigBean().isAllowCompleteDeviceCodeUri() || userCode == null) { return verifyUserCode(userCode, req, model);
// if we don't allow the complete URI or we didn't get a user code on the way in,
// print out a page that asks the user to enter their user code
// user must be logged in
return getRequestUserCodeViewName(req, model);
} else { } else {
// complete verification uri was used, we received user code directly return getRequestUserCodeViewName(req, model);
// skip requesting code page
// user must be logged in
return readUserCode(userCode, model, p, req, session);
} }
} }
@PreAuthorize("hasRole('ROLE_USER')") @PreAuthorize("hasRole('ROLE_USER')")
@RequestMapping(value = "/" + USER_URL + "/verify", method = RequestMethod.POST) @PostMapping(value = REQUEST_USER_CODE_URL)
public String readUserCode(@RequestParam(USER_CODE) String userCode, public String verifyUserCode(@RequestParam(value = USER_CODE) String userCode,
ModelMap model,
Principal p,
HttpServletRequest req, HttpServletRequest req,
HttpSession session) { ModelMap model)
{
model.put(USER_CODE, userCode);
DeviceCode dc = deviceCodeService.lookUpByUserCode(userCode); DeviceCode dc = deviceCodeService.lookUpByUserCode(userCode);
@ -266,30 +237,49 @@ public class DeviceEndpoint {
return errorViewName; return errorViewName;
} }
HttpSession session = req.getSession();
session.setAttribute(DEVICE_CODE_SESSION_ATTRIBUTE, dc);
return "redirect:" + CHECK_USER_CODE_URL + '?' + USER_CODE + '=' + userCode;
}
@PreAuthorize("hasRole('ROLE_USER')")
@GetMapping(value = CHECK_USER_CODE_URL)
public String startApproveDevice(@RequestParam(USER_CODE) String userCode,
ModelMap model,
Principal p,
HttpServletRequest req)
{
DeviceCode dc = deviceCodeService.lookUpByUserCode(userCode);
model.put(USER_CODE, userCode);
String errorViewName = checkDeviceCodeIsValid(dc, req, model);
if (errorViewName != null) {
return errorViewName;
}
ClientDetailsEntity client = clientService.loadClientByClientId(dc.getClientId()); ClientDetailsEntity client = clientService.loadClientByClientId(dc.getClientId());
model.put(CLIENT, client); model.put(CLIENT, client);
model.put(DC, dc); model.put(DC, dc);
model.put(SCOPES, getSortedScopes(dc));
HttpSession session = req.getSession();
AuthorizationRequest authorizationRequest = oAuth2RequestFactory.createAuthorizationRequest(dc.getRequestParameters()); AuthorizationRequest authorizationRequest = oAuth2RequestFactory.createAuthorizationRequest(dc.getRequestParameters());
session.setAttribute(AUTHORIZATION_REQUEST, authorizationRequest); session.setAttribute(AUTHORIZATION_REQUEST, authorizationRequest);
session.setAttribute(DEVICE_CODE_SESSION_ATTRIBUTE, dc);
return getApproveDeviceViewName(model, p, req, (DeviceCode) model.get(DC)); return getApproveDeviceViewName(model, p, req, dc);
} }
@PreAuthorize("hasRole('ROLE_USER')") @PreAuthorize("hasRole('ROLE_USER')")
@RequestMapping(value = "/" + USER_URL + "/approve", method = RequestMethod.POST) @PostMapping(value = DEVICE_APPROVED_URL)
public String approveDevice(@RequestParam(USER_CODE) String userCode, public String processApproveDevice(@RequestParam(USER_CODE) String userCode,
@RequestParam(value = USER_OAUTH_APPROVAL) Boolean approve, @RequestParam(value = USER_OAUTH_APPROVAL) Boolean approve,
Principal p, Principal p,
HttpServletRequest req, HttpServletRequest req,
ModelMap model, ModelMap model,
Authentication auth, Authentication auth)
HttpSession session) { {
HttpSession session = req.getSession();
AuthorizationRequest authorizationRequest = (AuthorizationRequest) session.getAttribute(AUTHORIZATION_REQUEST); AuthorizationRequest authorizationRequest = (AuthorizationRequest) session.getAttribute(AUTHORIZATION_REQUEST);
if (authorizationRequest == null) { if (authorizationRequest == null) {
throw new IllegalArgumentException("Authorization request not found in the session"); throw new IllegalArgumentException("Authorization request not found in the session");
@ -325,15 +315,13 @@ public class DeviceEndpoint {
OAuth2Request o2req = oAuth2RequestFactory.createOAuth2Request(authorizationRequest); OAuth2Request o2req = oAuth2RequestFactory.createOAuth2Request(authorizationRequest);
OAuth2Authentication o2Auth = new OAuth2Authentication(o2req, auth); OAuth2Authentication o2Auth = new OAuth2Authentication(o2req, auth);
DeviceCode approvedCode = deviceCodeService.approveDeviceCode(dc, o2Auth); deviceCodeService.approveDeviceCode(dc, o2Auth);
model.put(SCOPES, getSortedScopes(dc));
model.put(APPROVED, true); model.put(APPROVED, true);
return getDeviceApprovedViewName(req, model);
return DEVICE_APPROVED;
} }
private String handleRequest(String clientId, ClientWithScopes clientWithScopes, String scope, ModelMap model) { private String preprocessRequest(String clientId, ClientWithScopes clientWithScopes, String scope, ModelMap model) {
ClientDetailsEntity client; ClientDetailsEntity client;
try { try {
@ -360,10 +348,9 @@ public class DeviceEndpoint {
Set<String> allowedScopes = client.getScope(); Set<String> allowedScopes = client.getScope();
if (!scopeService.scopesMatch(allowedScopes, requestedScopes)) { if (!scopeService.scopesMatch(allowedScopes, requestedScopes)) {
log.error("Client asked for {} but is allowed {}", requestedScopes, allowedScopes); log.error("Client asked for {} but is allowed to request only {}", requestedScopes, allowedScopes);
model.put(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); model.put(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
model.put(JsonErrorView.ERROR, INVALID_SCOPE); model.put(JsonErrorView.ERROR, INVALID_SCOPE);
return JsonErrorView.VIEWNAME; return JsonErrorView.VIEWNAME;
} }
@ -402,18 +389,25 @@ public class DeviceEndpoint {
ControllerUtils.setPageOptions(model, req, htmlClasses, perunOidcConfig); ControllerUtils.setPageOptions(model, req, htmlClasses, perunOidcConfig);
model.put(PAGE, REQUEST_USER_CODE); model.put(PAGE, REQUEST_USER_CODE);
String shibAuthnContextClass = "";
model.put(ACR, shibAuthnContextClass);
return THEMED_REQUEST_USER_CODE; return THEMED_REQUEST_USER_CODE;
} }
private String getDeviceApprovedViewName(HttpServletRequest req, ModelMap model) {
if (perunOidcConfig.getTheme().equalsIgnoreCase(DEFAULT)) {
return DEVICE_APPROVED;
}
ControllerUtils.setPageOptions(model, req, htmlClasses, perunOidcConfig);
model.put(PAGE, DEVICE_APPROVED);
return THEMED_DEVICE_APPROVED;
}
private String getApproveDeviceViewName(ModelMap model, Principal p, HttpServletRequest req, DeviceCode dc) { private String getApproveDeviceViewName(ModelMap model, Principal p, HttpServletRequest req, DeviceCode dc) {
if (perunOidcConfig.getTheme().equalsIgnoreCase(DEFAULT)) { if (perunOidcConfig.getTheme().equalsIgnoreCase(DEFAULT)) {
model.put(SCOPES, ControllerUtils.getSortedScopes(dc.getScope(), scopeService));
return APPROVE_DEVICE; return APPROVE_DEVICE;
} }
model.remove(SCOPES);
ClientDetailsEntity client = (ClientDetailsEntity) model.get(CLIENT); ClientDetailsEntity client = (ClientDetailsEntity) model.get(CLIENT);
PerunUserInfo user = (PerunUserInfo) userInfoService.getByUsernameAndClientId( PerunUserInfo user = (PerunUserInfo) userInfoService.getByUsernameAndClientId(
@ -428,39 +422,15 @@ public class DeviceEndpoint {
return THEMED_APPROVE_DEVICE; return THEMED_APPROVE_DEVICE;
} }
private Set<SystemScope> getSortedScopes(DeviceCode dc) { private String constructVerificationURI(String uri, Map<String, String> params) throws URISyntaxException {
Set<SystemScope> scopes = scopeService.fromStrings(dc.getScope()); if (params == null || params.isEmpty()) {
Set<SystemScope> sortedScopes = new LinkedHashSet<>(scopes.size());
Set<SystemScope> systemScopes = scopeService.getAll();
for (SystemScope s : systemScopes) {
if (scopes.contains(s)) {
sortedScopes.add(s);
}
}
sortedScopes.addAll(Sets.difference(scopes, systemScopes));
return sortedScopes;
}
private String constructURI(String uri, Map<String, String> params) {
if (params.isEmpty()) {
return uri; return uri;
} }
StringBuilder uriBuilder = new StringBuilder(uri); URIBuilder builder = new URIBuilder(uri);
for (Map.Entry<String, String> param: params.entrySet()) {
if (!uri.contains("?")) { builder.addParameter(param.getKey(), param.getValue());
Optional<String> key = params.keySet().stream().findFirst();
uriBuilder.append("?").append(key).append("=").append(params.get(key));
params.remove(key);
} }
return builder.build().toString();
for (Map.Entry<String, String> entry : params.entrySet()) {
uriBuilder.append("&").append(entry.getKey()).append("=").append(entry.getValue());
}
return uriBuilder.toString();
} }
} }

View File

@ -166,7 +166,7 @@ public class OAuthConfirmationController {
model.put(CLIENT, client); model.put(CLIENT, client);
model.put(REDIRECT_URI, authRequest.getRedirectUri()); model.put(REDIRECT_URI, authRequest.getRedirectUri());
Set<SystemScope> sortedScopes = getSortedScopes(authRequest); Set<SystemScope> sortedScopes = ControllerUtils.getSortedScopes(authRequest.getScope(), scopeService);
model.put(SCOPES, sortedScopes); model.put(SCOPES, sortedScopes);
// get the userinfo claims for each scope // get the userinfo claims for each scope
@ -212,26 +212,6 @@ public class OAuthConfirmationController {
} }
} }
private Set<SystemScope> getSortedScopes(AuthorizationRequest authRequest) {
// pre-process the scopes
Set<SystemScope> scopes = scopeService.fromStrings(authRequest.getScope());
Set<SystemScope> sortedScopes = new LinkedHashSet<>(scopes.size());
Set<SystemScope> systemScopes = scopeService.getAll();
// sort scopes for display based on the inherent order of system scopes
for (SystemScope s : systemScopes) {
if (scopes.contains(s)) {
sortedScopes.add(s);
}
}
// add in any scopes that aren't system scopes to the end of the list
sortedScopes.addAll(Sets.difference(scopes, systemScopes));
return sortedScopes;
}
private Map<String, Map<String, String>> getClaimsForScopes(Principal p, Set<SystemScope> sortedScopes) { private Map<String, Map<String, String>> getClaimsForScopes(Principal p, Set<SystemScope> sortedScopes) {
UserInfo user = userInfoService.getByUsername(p.getName()); UserInfo user = userInfoService.getByUsername(p.getName());
Map<String, Map<String, String>> claimsForScopes = new HashMap<>(); Map<String, Map<String, String>> claimsForScopes = new HashMap<>();

View File

@ -1,5 +1,7 @@
package cz.muni.ics.oidc.saml; package cz.muni.ics.oidc.saml;
import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.AUTHORIZE_REQ_PATTERN;
import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.DEVICE_APPROVE_REQ_PATTERN;
import static org.springframework.http.HttpHeaders.REFERER; import static org.springframework.http.HttpHeaders.REFERER;
import java.io.IOException; import java.io.IOException;
@ -16,25 +18,30 @@ import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.filter.GenericFilterBean; import org.springframework.web.filter.GenericFilterBean;
@Slf4j @Slf4j
public class SamlInvalidateSessionFilter extends GenericFilterBean { public class SamlInvalidateSessionFilter extends GenericFilterBean {
private final AntPathRequestMatcher matcher; private static final RequestMatcher AUTHORIZE_MATCHER = new AntPathRequestMatcher(AUTHORIZE_REQ_PATTERN);
private static final RequestMatcher AUTHORIZE_ALL_MATCHER = new AntPathRequestMatcher(AUTHORIZE_REQ_PATTERN + "/**");
private static final RequestMatcher DEVICE_CODE_MATCHER = new AntPathRequestMatcher(DEVICE_APPROVE_REQ_PATTERN);
private static final RequestMatcher DEVICE_CODE_ALL_MATCHER = new AntPathRequestMatcher(DEVICE_APPROVE_REQ_PATTERN + "/**");
private static final RequestMatcher MATCHER = new OrRequestMatcher(
Arrays.asList(AUTHORIZE_MATCHER, AUTHORIZE_ALL_MATCHER, DEVICE_CODE_MATCHER, DEVICE_CODE_ALL_MATCHER));
private final SecurityContextLogoutHandler contextLogoutHandler; private final SecurityContextLogoutHandler contextLogoutHandler;
private final List<String> internalReferrers = new ArrayList<>(); private final List<String> internalReferrers = new ArrayList<>();
public SamlInvalidateSessionFilter(String pattern, public SamlInvalidateSessionFilter(String idpEntityId,
String idpEntityId,
String oidcIssuer, String oidcIssuer,
String proxySpEntityId, String proxySpEntityId,
SecurityContextLogoutHandler contextLogoutHandler, SecurityContextLogoutHandler contextLogoutHandler,
String[] internalReferrers) String[] internalReferrers)
{ {
this.matcher = new AntPathRequestMatcher(pattern);
if (StringUtils.hasText(idpEntityId)) { if (StringUtils.hasText(idpEntityId)) {
this.internalReferrers.add(idpEntityId); this.internalReferrers.add(idpEntityId);
} }
@ -60,9 +67,10 @@ public class SamlInvalidateSessionFilter extends GenericFilterBean {
{ {
HttpServletRequest req = (HttpServletRequest) request; HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response; HttpServletResponse res = (HttpServletResponse) response;
if (matcher.matches(req)) { if (MATCHER.matches(req)) {
boolean isDeviceCodeFlow = DEVICE_CODE_MATCHER.matches(req) || DEVICE_CODE_ALL_MATCHER.matches(req);
String referer = req.getHeader(REFERER); String referer = req.getHeader(REFERER);
if (!isInternalReferer(referer)) { if (!isInternalReferer(referer, !isDeviceCodeFlow)) {
log.debug("Got external referer, clear session to reauthenticate"); log.debug("Got external referer, clear session to reauthenticate");
contextLogoutHandler.logout(req, res, null); contextLogoutHandler.logout(req, res, null);
} }
@ -70,9 +78,9 @@ public class SamlInvalidateSessionFilter extends GenericFilterBean {
chain.doFilter(req, res); chain.doFilter(req, res);
} }
private boolean isInternalReferer(String referer) { private boolean isInternalReferer(String referer, boolean emptyRefererAsInternal) {
if (!StringUtils.hasText(referer)) { // no referer, consider as internal if (!StringUtils.hasText(referer)) { // no referer, consider as internal
return true; return emptyRefererAsInternal;
} }
for (String internal : internalReferrers) { for (String internal : internalReferrers) {
if (referer.startsWith(internal)) { if (referer.startsWith(internal)) {

View File

@ -1,5 +1,8 @@
package cz.muni.ics.oidc.server.filters; package cz.muni.ics.oidc.server.filters;
import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.AUTHORIZE_REQ_PATTERN;
import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.DEVICE_CHECK_CODE_REQ_PATTERN;
import cz.muni.ics.oauth2.model.ClientDetailsEntity; import cz.muni.ics.oauth2.model.ClientDetailsEntity;
import cz.muni.ics.oauth2.service.ClientDetailsEntityService; import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
import cz.muni.ics.oidc.BeanUtil; import cz.muni.ics.oidc.BeanUtil;
@ -8,6 +11,7 @@ import cz.muni.ics.oidc.models.PerunUser;
import cz.muni.ics.oidc.saml.SamlProperties; import cz.muni.ics.oidc.saml.SamlProperties;
import cz.muni.ics.oidc.server.adapters.PerunAdapter; import cz.muni.ics.oidc.server.adapters.PerunAdapter;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Properties; import java.util.Properties;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@ -19,6 +23,9 @@ import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.provider.OAuth2RequestFactory; import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.filter.GenericFilterBean; import org.springframework.web.filter.GenericFilterBean;
@ -31,6 +38,13 @@ import org.springframework.web.filter.GenericFilterBean;
@Slf4j @Slf4j
public class CallPerunFiltersFilter extends GenericFilterBean { public class CallPerunFiltersFilter extends GenericFilterBean {
private static final RequestMatcher AUTHORIZE_MATCHER = new AntPathRequestMatcher(AUTHORIZE_REQ_PATTERN);
private static final RequestMatcher AUTHORIZE_ALL_MATCHER = new AntPathRequestMatcher(AUTHORIZE_REQ_PATTERN + "/**");
private static final RequestMatcher DEVICE_CODE_MATCHER = new AntPathRequestMatcher(DEVICE_CHECK_CODE_REQ_PATTERN);
private static final RequestMatcher DEVICE_CODE_ALL_MATCHER = new AntPathRequestMatcher(DEVICE_CHECK_CODE_REQ_PATTERN + "/**");
private static final RequestMatcher MATCHER = new OrRequestMatcher(
Arrays.asList(AUTHORIZE_MATCHER, AUTHORIZE_ALL_MATCHER, DEVICE_CODE_MATCHER, DEVICE_CODE_ALL_MATCHER));
@Autowired @Autowired
private Properties coreProperties; private Properties coreProperties;
@ -60,9 +74,12 @@ public class CallPerunFiltersFilter extends GenericFilterBean {
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException throws IOException, ServletException
{ {
HttpServletRequest request = (HttpServletRequest) servletRequest;
if (!MATCHER.matches(request)) {
log.debug("Custom filters have been skipped, did not match '/authorize' nor '/device/code' request");
} else {
List<PerunRequestFilter> filters = perunFiltersContext.getFilters(); List<PerunRequestFilter> filters = perunFiltersContext.getFilters();
if (filters != null && !filters.isEmpty()) { if (filters != null && !filters.isEmpty()) {
HttpServletRequest request = (HttpServletRequest) servletRequest;
ClientDetailsEntity client = FiltersUtils.extractClientFromRequest(request, authRequestFactory, ClientDetailsEntity client = FiltersUtils.extractClientFromRequest(request, authRequestFactory,
clientDetailsEntityService); clientDetailsEntityService);
Facility facility = null; Facility facility = null;
@ -74,7 +91,8 @@ public class CallPerunFiltersFilter extends GenericFilterBean {
CallPerunFiltersFilter.class.getSimpleName(), client.getClientId(), e); CallPerunFiltersFilter.class.getSimpleName(), client.getClientId(), e);
} }
} }
PerunUser user = FiltersUtils.getPerunUser(request, perunAdapter, samlProperties.getUserIdentifierAttribute()); PerunUser user = FiltersUtils.getPerunUser(request, perunAdapter,
samlProperties.getUserIdentifierAttribute());
FilterParams params = new FilterParams(client, facility, user); FilterParams params = new FilterParams(client, facility, user);
for (PerunRequestFilter filter : filters) { for (PerunRequestFilter filter : filters) {
if (!filter.doFilter(servletRequest, servletResponse, params)) { if (!filter.doFilter(servletRequest, servletResponse, params)) {
@ -82,6 +100,7 @@ public class CallPerunFiltersFilter extends GenericFilterBean {
} }
} }
} }
}
filterChain.doFilter(servletRequest, servletResponse); filterChain.doFilter(servletRequest, servletResponse);
} }

View File

@ -1,9 +1,10 @@
package cz.muni.ics.oidc.server.filters; package cz.muni.ics.oidc.server.filters;
import static cz.muni.ics.oauth2.web.DeviceEndpoint.DEVICE_CODE_SESSION_ATTRIBUTE;
import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.PARAM_FORCE_AUTHN; import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.PARAM_FORCE_AUTHN;
import com.google.common.base.Strings;
import cz.muni.ics.oauth2.model.ClientDetailsEntity; import cz.muni.ics.oauth2.model.ClientDetailsEntity;
import cz.muni.ics.oauth2.model.DeviceCode;
import cz.muni.ics.oauth2.service.ClientDetailsEntityService; import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
import cz.muni.ics.oidc.models.Facility; import cz.muni.ics.oidc.models.Facility;
import cz.muni.ics.oidc.models.PerunAttributeValue; import cz.muni.ics.oidc.models.PerunAttributeValue;
@ -20,13 +21,9 @@ import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.oauth2.provider.AuthorizationRequest;
import org.springframework.security.oauth2.provider.OAuth2RequestFactory; import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.security.providers.ExpiringUsernameAuthenticationToken; import org.springframework.security.providers.ExpiringUsernameAuthenticationToken;
import org.springframework.security.saml.SAMLCredential; import org.springframework.security.saml.SAMLCredential;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
@ -37,8 +34,6 @@ import org.springframework.util.StringUtils;
@Slf4j @Slf4j
public class FiltersUtils { public class FiltersUtils {
private static final RequestMatcher requestMatcher = new AntPathRequestMatcher(PerunFilterConstants.AUTHORIZE_REQ_PATTERN);
/** /**
* Create map of request params in format key = name, value = paramValue. * Create map of request params in format key = name, value = paramValue.
* *
@ -69,21 +64,29 @@ public class FiltersUtils {
OAuth2RequestFactory authRequestFactory, OAuth2RequestFactory authRequestFactory,
ClientDetailsEntityService clientService) ClientDetailsEntityService clientService)
{ {
if (!requestMatcher.matches(request) || request.getParameter("response_type") == null) { if (request.getParameter("response_type") == null
&& request.getSession() == null
&& request.getSession().getAttribute(DEVICE_CODE_SESSION_ATTRIBUTE) == null
) {
return null; return null;
} }
AuthorizationRequest authRequest = authRequestFactory.createAuthorizationRequest( String clientId;
FiltersUtils.createRequestMap(request.getParameterMap())); if (request.getSession() != null && request.getSession().getAttribute(DEVICE_CODE_SESSION_ATTRIBUTE) != null) {
clientId = ((DeviceCode) request.getSession().getAttribute(DEVICE_CODE_SESSION_ATTRIBUTE)).getClientId();
} else {
clientId = authRequestFactory.createAuthorizationRequest(
FiltersUtils.createRequestMap(request.getParameterMap())).getClientId();
}
ClientDetailsEntity client; ClientDetailsEntity client;
if (Strings.isNullOrEmpty(authRequest.getClientId())) { if (!StringUtils.hasText(clientId)) {
log.debug("cannot extract client - ClientID is null or empty"); log.debug("cannot extract client - ClientID is null or empty");
return null; return null;
} }
client = clientService.loadClientByClientId(authRequest.getClientId()); client = clientService.loadClientByClientId(clientId);
if (Strings.isNullOrEmpty(client.getClientName())) { if (!StringUtils.hasText(client.getClientName())) {
log.warn("cannot extract clientName for the clientID '{}'", client.getClientId()); log.warn("cannot extract clientName for the clientID '{}'", client.getClientId());
return null; return null;
} }
@ -207,11 +210,11 @@ public class FiltersUtils {
/** /**
* Redirect user to the unapproved page. * Redirect user to the unapproved page.
* @param request original request object * @param base Base URL
* @param response response object * @param response response object
* @param clientId identifier of the service * @param clientId identifier of the service
*/ */
public static void redirectUnapproved(HttpServletRequest request, HttpServletResponse response, String clientId, String redirectMapping) public static void redirectUnapproved(String base, HttpServletResponse response, String clientId, String redirectMapping)
{ {
// cannot register, redirect to unapproved // cannot register, redirect to unapproved
Map<String, String> params = new HashMap<>(); Map<String, String> params = new HashMap<>();
@ -219,8 +222,7 @@ public class FiltersUtils {
params.put("client_id", clientId); params.put("client_id", clientId);
} }
String redirectUrl = ControllerUtils.createRedirectUrl(request, PerunFilterConstants.AUTHORIZE_REQ_PATTERN, String redirectUrl = ControllerUtils.createRedirectUrl(base, redirectMapping, params);
redirectMapping, params);
response.reset(); response.reset();
response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
response.setHeader("Location", redirectUrl); response.setHeader("Location", redirectUrl);
@ -228,7 +230,7 @@ public class FiltersUtils {
/** /**
* Redirect user to the correct page when cannot access the service based on membership. * Redirect user to the correct page when cannot access the service based on membership.
* @param request Request object * @param base base URL
* @param response Response object * @param response Response object
* @param facility Facility representing the client * @param facility Facility representing the client
* @param user User accessing the service * @param user User accessing the service
@ -237,7 +239,7 @@ public class FiltersUtils {
* @param facilityAttributes Actual facility attributes * @param facilityAttributes Actual facility attributes
* @param perunAdapter Adapter to call Perun * @param perunAdapter Adapter to call Perun
*/ */
public static void redirectUserCannotAccess(HttpServletRequest request, public static void redirectUserCannotAccess(String base,
HttpServletResponse response, HttpServletResponse response,
Facility facility, Facility facility,
PerunUser user, PerunUser user,
@ -263,7 +265,7 @@ public class FiltersUtils {
if (facilityAttributes.get(facilityAttrsConfig.getDynamicRegistrationAttr()).valueAsBoolean()) { if (facilityAttributes.get(facilityAttrsConfig.getDynamicRegistrationAttr()).valueAsBoolean()) {
// redirect to registration form // redirect to registration form
FiltersUtils.redirectToRegistrationForm(request, response, clientIdentifier, facility, user); FiltersUtils.redirectToRegistrationForm(base, response, clientIdentifier, facility, user);
return; return;
} }
} }
@ -271,7 +273,7 @@ public class FiltersUtils {
// cannot register, redirect to unapproved // cannot register, redirect to unapproved
log.debug("user cannot register to obtain access, redirecting user '{}' to unapproved page", user); log.debug("user cannot register to obtain access, redirecting user '{}' to unapproved page", user);
FiltersUtils.redirectUnapproved(request, response, clientIdentifier, redirectUrl); FiltersUtils.redirectUnapproved(base, response, clientIdentifier, redirectUrl);
} }
public static String fillStringMandatoryProperty(String propertyName, public static String fillStringMandatoryProperty(String propertyName,
@ -286,13 +288,13 @@ public class FiltersUtils {
return filled; return filled;
} }
private static void redirectToRegistrationForm(HttpServletRequest request, HttpServletResponse response, private static void redirectToRegistrationForm(String base, HttpServletResponse response,
String clientIdentifier, Facility facility, PerunUser user) { String clientIdentifier, Facility facility, PerunUser user) {
Map<String, String> params = new HashMap<>(); Map<String, String> params = new HashMap<>();
params.put("client_id", clientIdentifier); params.put("client_id", clientIdentifier);
params.put("facility_id", facility.getId().toString()); params.put("facility_id", facility.getId().toString());
params.put("user_id", String.valueOf(user.getId())); params.put("user_id", String.valueOf(user.getId()));
String redirectUrl = ControllerUtils.createRedirectUrl(request, PerunFilterConstants.AUTHORIZE_REQ_PATTERN, String redirectUrl = ControllerUtils.createRedirectUrl(base,
PerunUnapprovedRegistrationController.REGISTRATION_CONTINUE_MAPPING, params); PerunUnapprovedRegistrationController.REGISTRATION_CONTINUE_MAPPING, params);
log.debug("redirecting user '{}' to the registration form URL: {}", user, redirectUrl); log.debug("redirecting user '{}' to the registration form URL: {}", user, redirectUrl);
response.reset(); response.reset();

View File

@ -12,9 +12,8 @@ import java.util.Map;
public class PerunFilterConstants { public class PerunFilterConstants {
public static final String AUTHORIZE_REQ_PATTERN = "/authorize"; public static final String AUTHORIZE_REQ_PATTERN = "/authorize";
public static final String SHIB_IDENTITY_PROVIDER = "Shib-Identity-Provider"; public static final String DEVICE_APPROVE_REQ_PATTERN = "/device/code";
public static final String SHIB_AUTHN_CONTEXT_CLASS = "Shib-AuthnContext-Class"; public static final String DEVICE_CHECK_CODE_REQ_PATTERN = "/device/checkcode";
public static final String SHIB_AUTHN_CONTEXT_METHOD = "Shib-Authentication-Method";
public static final String PARAM_CLIENT_ID = "client_id"; public static final String PARAM_CLIENT_ID = "client_id";
public static final String PARAM_SCOPE = "scope"; public static final String PARAM_SCOPE = "scope";

View File

@ -1,6 +1,8 @@
package cz.muni.ics.oidc.server.filters; package cz.muni.ics.oidc.server.filters;
import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.AUTHORIZE_REQ_PATTERN; import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.AUTHORIZE_REQ_PATTERN;
import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.DEVICE_APPROVE_REQ_PATTERN;
import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.DEVICE_CHECK_CODE_REQ_PATTERN;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
@ -11,6 +13,7 @@ import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher;
/** /**
@ -42,8 +45,6 @@ public abstract class PerunRequestFilter {
private static final String CLIENT_IDS = "clientIds"; private static final String CLIENT_IDS = "clientIds";
private static final String SUBS = "subs"; private static final String SUBS = "subs";
private static final RequestMatcher requestMatcher = new AntPathRequestMatcher(AUTHORIZE_REQ_PATTERN);
private final String filterName; private final String filterName;
private Set<String> clientIds = new HashSet<>(); private Set<String> clientIds = new HashSet<>();
private Set<String> subs = new HashSet<>(); private Set<String> subs = new HashSet<>();
@ -77,11 +78,6 @@ public abstract class PerunRequestFilter {
public boolean doFilter(ServletRequest req, ServletResponse res, FilterParams params) throws IOException { public boolean doFilter(ServletRequest req, ServletResponse res, FilterParams params) throws IOException {
HttpServletRequest request = (HttpServletRequest) req; HttpServletRequest request = (HttpServletRequest) req;
// skip everything that's not an authorize URL
if (!requestMatcher.matches(request)) {
log.debug("{} - filter has been skipped, did not match '/authorize' the request", filterName);
return true;
}
if (!skip(request)) { if (!skip(request)) {
log.trace("{} - executing filter", filterName); log.trace("{} - executing filter", filterName);
return this.process(req, res, params); return this.process(req, res, params);

View File

@ -6,6 +6,7 @@ import cz.muni.ics.oidc.models.PerunAttributeValue;
import cz.muni.ics.oidc.models.PerunUser; import cz.muni.ics.oidc.models.PerunUser;
import cz.muni.ics.oidc.server.adapters.PerunAdapter; import cz.muni.ics.oidc.server.adapters.PerunAdapter;
import cz.muni.ics.oidc.server.configurations.FacilityAttrsConfig; import cz.muni.ics.oidc.server.configurations.FacilityAttrsConfig;
import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
import cz.muni.ics.oidc.server.filters.FilterParams; import cz.muni.ics.oidc.server.filters.FilterParams;
import cz.muni.ics.oidc.server.filters.FiltersUtils; import cz.muni.ics.oidc.server.filters.FiltersUtils;
import cz.muni.ics.oidc.server.filters.PerunRequestFilter; import cz.muni.ics.oidc.server.filters.PerunRequestFilter;
@ -35,6 +36,7 @@ public class PerunAuthorizationFilter extends PerunRequestFilter {
private final PerunAdapter perunAdapter; private final PerunAdapter perunAdapter;
private final FacilityAttrsConfig facilityAttrsConfig; private final FacilityAttrsConfig facilityAttrsConfig;
private final String filterName; private final String filterName;
private final PerunOidcConfig config;
public PerunAuthorizationFilter(PerunRequestFilterParams params) { public PerunAuthorizationFilter(PerunRequestFilterParams params) {
super(params); super(params);
@ -42,6 +44,7 @@ public class PerunAuthorizationFilter extends PerunRequestFilter {
this.perunAdapter = beanUtil.getBean(PerunAdapter.class); this.perunAdapter = beanUtil.getBean(PerunAdapter.class);
this.facilityAttrsConfig = beanUtil.getBean(FacilityAttrsConfig.class); this.facilityAttrsConfig = beanUtil.getBean(FacilityAttrsConfig.class);
this.filterName = params.getFilterName(); this.filterName = params.getFilterName();
this.config = beanUtil.getBean(PerunOidcConfig.class);
} }
@Override @Override
@ -81,7 +84,7 @@ public class PerunAuthorizationFilter extends PerunRequestFilter {
log.info("{} - user allowed to access the service", filterName); log.info("{} - user allowed to access the service", filterName);
return true; return true;
} else { } else {
FiltersUtils.redirectUserCannotAccess(request, response, facility, user, clientIdentifier, FiltersUtils.redirectUserCannotAccess(config.getConfigBean().getIssuer(), response, facility, user, clientIdentifier,
facilityAttrsConfig, facilityAttributes, perunAdapter, facilityAttrsConfig, facilityAttributes, perunAdapter,
PerunUnapprovedController.UNAPPROVED_AUTHORIZATION); PerunUnapprovedController.UNAPPROVED_AUTHORIZATION);
return false; return false;

View File

@ -11,6 +11,7 @@ import cz.muni.ics.oidc.BeanUtil;
import cz.muni.ics.oidc.models.PerunAttributeValue; import cz.muni.ics.oidc.models.PerunAttributeValue;
import cz.muni.ics.oidc.models.PerunUser; import cz.muni.ics.oidc.models.PerunUser;
import cz.muni.ics.oidc.server.adapters.PerunAdapter; import cz.muni.ics.oidc.server.adapters.PerunAdapter;
import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
import cz.muni.ics.oidc.server.filters.FilterParams; import cz.muni.ics.oidc.server.filters.FilterParams;
import cz.muni.ics.oidc.server.filters.FiltersUtils; import cz.muni.ics.oidc.server.filters.FiltersUtils;
import cz.muni.ics.oidc.server.filters.PerunFilterConstants; import cz.muni.ics.oidc.server.filters.PerunFilterConstants;
@ -58,12 +59,14 @@ public class PerunIsCesnetEligibleFilter extends PerunRequestFilter {
private final int validityPeriod; private final int validityPeriod;
/* END OF CONFIGURATION PROPERTIES */ /* END OF CONFIGURATION PROPERTIES */
private final PerunOidcConfig config;
private final PerunAdapter perunAdapter; private final PerunAdapter perunAdapter;
private final String filterName; private final String filterName;
public PerunIsCesnetEligibleFilter(PerunRequestFilterParams params) { public PerunIsCesnetEligibleFilter(PerunRequestFilterParams params) {
super(params); super(params);
BeanUtil beanUtil = params.getBeanUtil(); BeanUtil beanUtil = params.getBeanUtil();
this.config = beanUtil.getBean(PerunOidcConfig.class);
this.perunAdapter = beanUtil.getBean(PerunAdapter.class); this.perunAdapter = beanUtil.getBean(PerunAdapter.class);
this.isCesnetEligibleAttrName = params.getProperty(IS_CESNET_ELIGIBLE_ATTR_NAME); this.isCesnetEligibleAttrName = params.getProperty(IS_CESNET_ELIGIBLE_ATTR_NAME);
this.triggerScope = params.getProperty(IS_CESNET_ELIGIBLE_SCOPE); this.triggerScope = params.getProperty(IS_CESNET_ELIGIBLE_SCOPE);
@ -132,7 +135,7 @@ public class PerunIsCesnetEligibleFilter extends PerunRequestFilter {
params.put(PARAM_TARGET, targetURL); params.put(PARAM_TARGET, targetURL);
params.put(PARAM_REASON, reason); params.put(PARAM_REASON, reason);
String redirectUrl = ControllerUtils.createRedirectUrl(req, PerunFilterConstants.AUTHORIZE_REQ_PATTERN, String redirectUrl = ControllerUtils.createRedirectUrl(config.getConfigBean().getIssuer(),
PerunUnapprovedController.UNAPPROVED_IS_CESNET_ELIGIBLE_MAPPING, params); PerunUnapprovedController.UNAPPROVED_IS_CESNET_ELIGIBLE_MAPPING, params);
log.debug("{} - redirecting user to unapproved: URL '{}'", filterName, redirectUrl); log.debug("{} - redirecting user to unapproved: URL '{}'", filterName, redirectUrl);
res.reset(); res.reset();

View File

@ -7,6 +7,7 @@ import cz.muni.ics.oidc.BeanUtil;
import cz.muni.ics.oidc.models.Facility; import cz.muni.ics.oidc.models.Facility;
import cz.muni.ics.oidc.models.PerunAttributeValue; import cz.muni.ics.oidc.models.PerunAttributeValue;
import cz.muni.ics.oidc.server.adapters.PerunAdapter; import cz.muni.ics.oidc.server.adapters.PerunAdapter;
import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
import cz.muni.ics.oidc.server.filters.FilterParams; import cz.muni.ics.oidc.server.filters.FilterParams;
import cz.muni.ics.oidc.server.filters.FiltersUtils; import cz.muni.ics.oidc.server.filters.FiltersUtils;
import cz.muni.ics.oidc.server.filters.PerunFilterConstants; import cz.muni.ics.oidc.server.filters.PerunFilterConstants;
@ -43,6 +44,7 @@ public class PerunIsTestSpFilter extends PerunRequestFilter {
private final String isTestSpAttrName; private final String isTestSpAttrName;
private final PerunAdapter perunAdapter; private final PerunAdapter perunAdapter;
private final String filterName; private final String filterName;
private final PerunOidcConfig config;
public PerunIsTestSpFilter(PerunRequestFilterParams params) { public PerunIsTestSpFilter(PerunRequestFilterParams params) {
super(params); super(params);
@ -50,6 +52,7 @@ public class PerunIsTestSpFilter extends PerunRequestFilter {
this.perunAdapter = beanUtil.getBean(PerunAdapter.class); this.perunAdapter = beanUtil.getBean(PerunAdapter.class);
this.isTestSpAttrName = params.getProperty(IS_TEST_SP_ATTR_NAME); this.isTestSpAttrName = params.getProperty(IS_TEST_SP_ATTR_NAME);
this.filterName = params.getFilterName(); this.filterName = params.getFilterName();
this.config = beanUtil.getBean(PerunOidcConfig.class);
} }
@Override @Override
@ -95,7 +98,7 @@ public class PerunIsTestSpFilter extends PerunRequestFilter {
Map<String, String> params = new HashMap<>(); Map<String, String> params = new HashMap<>();
params.put(PARAM_TARGET, targetURL); params.put(PARAM_TARGET, targetURL);
String redirectUrl = ControllerUtils.createRedirectUrl(req, PerunFilterConstants.AUTHORIZE_REQ_PATTERN, String redirectUrl = ControllerUtils.createRedirectUrl(config.getConfigBean().getIssuer(),
IsTestSpController.MAPPING, params); IsTestSpController.MAPPING, params);
log.debug("{} - redirecting user to testSP warning page: {}", filterName, redirectUrl); log.debug("{} - redirecting user to testSP warning page: {}", filterName, redirectUrl);
res.reset(); res.reset();

View File

@ -6,6 +6,7 @@ import cz.muni.ics.oidc.models.PerunAttributeValue;
import cz.muni.ics.oidc.models.PerunUser; import cz.muni.ics.oidc.models.PerunUser;
import cz.muni.ics.oidc.server.adapters.PerunAdapter; import cz.muni.ics.oidc.server.adapters.PerunAdapter;
import cz.muni.ics.oidc.server.configurations.FacilityAttrsConfig; import cz.muni.ics.oidc.server.configurations.FacilityAttrsConfig;
import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
import cz.muni.ics.oidc.server.filters.FilterParams; import cz.muni.ics.oidc.server.filters.FilterParams;
import cz.muni.ics.oidc.server.filters.FiltersUtils; import cz.muni.ics.oidc.server.filters.FiltersUtils;
import cz.muni.ics.oidc.server.filters.PerunRequestFilter; import cz.muni.ics.oidc.server.filters.PerunRequestFilter;
@ -66,6 +67,7 @@ public class ValidUserFilter extends PerunRequestFilter {
private final PerunAdapter perunAdapter; private final PerunAdapter perunAdapter;
private final FacilityAttrsConfig facilityAttrsConfig; private final FacilityAttrsConfig facilityAttrsConfig;
private final String filterName; private final String filterName;
private final PerunOidcConfig config;
public ValidUserFilter(PerunRequestFilterParams params) { public ValidUserFilter(PerunRequestFilterParams params) {
super(params); super(params);
@ -80,6 +82,7 @@ public class ValidUserFilter extends PerunRequestFilter {
this.prodEnvGroups = this.getIdsFromParam(params, PROD_ENV_GROUPS); this.prodEnvGroups = this.getIdsFromParam(params, PROD_ENV_GROUPS);
this.prodEnvVos = this.getIdsFromParam(params, PROD_ENV_VOS); this.prodEnvVos = this.getIdsFromParam(params, PROD_ENV_VOS);
this.filterName = params.getFilterName(); this.filterName = params.getFilterName();
this.config = beanUtil.getBean(PerunOidcConfig.class);
} }
@Override @Override
@ -103,7 +106,7 @@ public class ValidUserFilter extends PerunRequestFilter {
return true; return true;
} }
if (!checkMemberValidInGroupsAndVos(user, facility, request, response, params, allEnvVos, allEnvGroups, if (!checkMemberValidInGroupsAndVos(user, facility, response, params, allEnvVos, allEnvGroups,
PerunUnapprovedController.UNAPPROVED_NOT_IN_MANDATORY_VOS_GROUPS)) { PerunUnapprovedController.UNAPPROVED_NOT_IN_MANDATORY_VOS_GROUPS)) {
return false; return false;
} }
@ -118,7 +121,7 @@ public class ValidUserFilter extends PerunRequestFilter {
additionalVos.addAll(testEnvVos); additionalVos.addAll(testEnvVos);
additionalGroups.addAll(testEnvGroups); additionalGroups.addAll(testEnvGroups);
if (!checkMemberValidInGroupsAndVos(user, facility, request, response, params, additionalVos, if (!checkMemberValidInGroupsAndVos(user, facility, response, params, additionalVos,
additionalGroups, PerunUnapprovedController.UNAPPROVED_NOT_IN_TEST_VOS_GROUPS)) { additionalGroups, PerunUnapprovedController.UNAPPROVED_NOT_IN_TEST_VOS_GROUPS)) {
return false; return false;
} }
@ -126,7 +129,7 @@ public class ValidUserFilter extends PerunRequestFilter {
additionalVos.addAll(prodEnvVos); additionalVos.addAll(prodEnvVos);
additionalGroups.addAll(prodEnvGroups); additionalGroups.addAll(prodEnvGroups);
if (!checkMemberValidInGroupsAndVos(user, facility, request, response, params, additionalVos, if (!checkMemberValidInGroupsAndVos(user, facility, response, params, additionalVos,
additionalGroups, PerunUnapprovedController.UNAPPROVED_NOT_IN_PROD_VOS_GROUPS)) { additionalGroups, PerunUnapprovedController.UNAPPROVED_NOT_IN_PROD_VOS_GROUPS)) {
return false; return false;
} }
@ -153,7 +156,6 @@ public class ValidUserFilter extends PerunRequestFilter {
private boolean checkMemberValidInGroupsAndVos( private boolean checkMemberValidInGroupsAndVos(
PerunUser user, PerunUser user,
Facility facility, Facility facility,
HttpServletRequest request,
HttpServletResponse response, HttpServletResponse response,
FilterParams params, FilterParams params,
Set<Long> vos, Set<Long> vos,
@ -168,8 +170,8 @@ public class ValidUserFilter extends PerunRequestFilter {
Map<String, PerunAttributeValue> facilityAttributes = perunAdapter.getFacilityAttributeValues( Map<String, PerunAttributeValue> facilityAttributes = perunAdapter.getFacilityAttributeValues(
facility, facilityAttrsConfig.getMembershipAttrNames()); facility, facilityAttrsConfig.getMembershipAttrNames());
FiltersUtils.redirectUserCannotAccess(request, response, facility, user, params.getClientIdentifier(), FiltersUtils.redirectUserCannotAccess(config.getConfigBean().getIssuer(), response, facility, user,
facilityAttrsConfig, facilityAttributes, perunAdapter, redirectUrl); params.getClientIdentifier(), facilityAttrsConfig, facilityAttributes, perunAdapter, redirectUrl);
return false; return false;
} }

View File

@ -84,23 +84,24 @@ public class ControllerUtils {
/** /**
* Create redirect URL. * Create redirect URL.
* *
* @param request Request object * @param base Base of URL, might include some path already
* @param removedPart Part of URL to be removed * @param path What to include as Path
* @param pathPart What to include as Path
* @param params Map object of parameters * @param params Map object of parameters
* @return Modified redirect URL * @return Modified redirect URL
*/ */
public static String createRedirectUrl(HttpServletRequest request, String removedPart, public static String createRedirectUrl(String base,
String pathPart, Map<String, String> params) { String path,
String baseUrl = request.getRequestURL().toString(); Map<String, String> params)
int endIndex = baseUrl.indexOf(removedPart); {
if (endIndex > 1) {
baseUrl = baseUrl.substring(0, endIndex);
}
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
builder.append(baseUrl); if (!base.endsWith("/")) {
builder.append(pathPart); base += '/';
}
builder.append(base);
if (path.startsWith("/")) {
path = path.substring(1);
}
builder.append(path);
if (!params.isEmpty()) { if (!params.isEmpty()) {
builder.append('?'); builder.append('?');
for (Map.Entry<String, String> entry : params.entrySet()) { for (Map.Entry<String, String> entry : params.entrySet()) {
@ -292,4 +293,20 @@ public class ControllerUtils {
} }
} }
public static Set<SystemScope> getSortedScopes(Set<String> requestedScopes, SystemScopeService scopeService) {
Set<SystemScope> scopes = scopeService.fromStrings(requestedScopes);
Set<SystemScope> sortedScopes = new LinkedHashSet<>(scopes.size());
Set<SystemScope> systemScopes = scopeService.getAll();
for (SystemScope s : systemScopes) {
if (scopes.contains(s)) {
sortedScopes.add(s);
}
}
sortedScopes.addAll(Sets.difference(scopes, systemScopes));
return sortedScopes;
}
} }

View File

@ -91,7 +91,7 @@ public class PerunUnapprovedRegistrationController {
log.debug("groupsForReg: {}", groupsForRegistration); log.debug("groupsForReg: {}", groupsForRegistration);
if (groupsForRegistration.isEmpty()) { if (groupsForRegistration.isEmpty()) {
String redirectUrl = ControllerUtils.createRedirectUrl(request, REGISTRATION_FORM_MAPPING, String redirectUrl = ControllerUtils.createRedirectUrl(perunOidcConfig.getConfigBean().getIssuer(),
PerunUnapprovedController.UNAPPROVED_MAPPING, Collections.singletonMap("client_id", clientId)); PerunUnapprovedController.UNAPPROVED_MAPPING, Collections.singletonMap("client_id", clientId));
response.reset(); response.reset();
response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);

View File

@ -75,6 +75,15 @@ public class ConfigurationPropertiesBean {
return issuer; return issuer;
} }
public String getIssuer(boolean trailingSlash) {
String iss = issuer.endsWith("/") ? issuer.substring(0, issuer.length() - 1) : issuer;
if (trailingSlash) {
return iss + '/';
} else {
return iss;
}
}
public void setIssuer(String iss) { public void setIssuer(String iss) {
issuer = iss; issuer = iss;
} }