diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/application-context.xml b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/application-context.xml index 2bc7eaf1f..c16a9cf0c 100644 --- a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/application-context.xml +++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/application-context.xml @@ -65,10 +65,12 @@ - + + + - + @@ -83,7 +85,7 @@ - + @@ -189,7 +191,7 @@ - - + + + @@ -82,7 +84,7 @@ - + @@ -495,13 +497,13 @@ - + diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/approveDevice.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/approveDevice.jsp index a1c70aa66..4a5462ba5 100644 --- a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/approveDevice.jsp +++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/approveDevice.jsp @@ -37,7 +37,7 @@
+ action="${ config.issuer }${ config.issuer.endsWith('/') ? '' : '/' }device/approved" method="post">
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/requestUserCode.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/requestUserCode.jsp index 9551acbff..df4dd18e2 100644 --- a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/requestUserCode.jsp +++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/requestUserCode.jsp @@ -39,14 +39,15 @@ - +
- +
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/themedApproveDevice.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/themedApproveDevice.jsp index bb532ecfa..80b75a830 100644 --- a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/themedApproveDevice.jsp +++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/themedApproveDevice.jsp @@ -33,7 +33,7 @@
+ action="${ config.issuer }${ config.issuer.endsWith('/') ? '' : '/' }device/approved" method="post">

${" "}${fn:escapeXml(client.clientName)} diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/themedDeviceApproved.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/themedDeviceApproved.jsp index b6a04af6d..b5df22a50 100644 --- a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/themedDeviceApproved.jsp +++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/themedDeviceApproved.jsp @@ -43,7 +43,7 @@ pageContext.setAttribute("cssLinks", cssLinks); ${". "} -

q +

<%-- wrap --%> diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/themedRequestUserCode.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/themedRequestUserCode.jsp index ce94d3c3d..b3130f9f8 100644 --- a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/themedRequestUserCode.jsp +++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/themedRequestUserCode.jsp @@ -51,15 +51,15 @@ - +
+ autocapitalize="off" autocomplete="off" spellcheck="false" value="${user_code}" />
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/discovery/web/DiscoveryEndpoint.java b/perun-oidc-server/src/main/java/cz/muni/ics/discovery/web/DiscoveryEndpoint.java index fffa20e04..0a85af3fe 100644 --- a/perun-oidc-server/src/main/java/cz/muni/ics/discovery/web/DiscoveryEndpoint.java +++ b/perun-oidc-server/src/main/java/cz/muni/ics/discovery/web/DiscoveryEndpoint.java @@ -346,7 +346,7 @@ public class DiscoveryEndpoint { 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); diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/DeviceEndpoint.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/DeviceEndpoint.java index ebe970b47..4a15276a6 100644 --- a/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/DeviceEndpoint.java +++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/DeviceEndpoint.java @@ -16,34 +16,29 @@ package cz.muni.ics.oauth2.web; -import com.google.common.collect.Sets; import cz.muni.ics.oauth2.exception.DeviceCodeCreationException; import cz.muni.ics.oauth2.model.ClientDetailsEntity; import cz.muni.ics.oauth2.model.ClientWithScopes; 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.DeviceCodeService; import cz.muni.ics.oauth2.service.SystemScopeService; 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.userInfo.PerunUserInfo; import cz.muni.ics.oidc.web.WebHtmlClasses; 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.view.HttpCodeView; import cz.muni.ics.openid.connect.view.JsonEntityView; import cz.muni.ics.openid.connect.view.JsonErrorView; -import java.net.URI; import java.net.URISyntaxException; import java.security.Principal; import java.util.Collection; import java.util.Date; import java.util.HashMap; -import java.util.LinkedHashSet; import java.util.Map; -import java.util.Optional; import java.util.Set; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; @@ -64,10 +59,10 @@ import org.springframework.security.oauth2.provider.OAuth2RequestFactory; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.util.StringUtils; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; 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 @@ -87,6 +82,7 @@ public class DeviceEndpoint { public static final String APPROVE_DEVICE = "approveDevice"; public static final String THEMED_APPROVE_DEVICE = "themedApproveDevice"; public static final String DEVICE_APPROVED = "deviceApproved"; + public static final String THEMED_DEVICE_APPROVED = "themedDeviceApproved"; // response keys 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 SCOPES = "scopes"; public static final String PAGE = "page"; - public static final String ACR = "acr"; public static final String ERROR = "error"; // session attributes @@ -123,8 +118,10 @@ public class DeviceEndpoint { // other public static final String DEFAULT = "default"; - public static final String URL = "devicecode"; - public static final String USER_URL = "device"; + public static final String ENDPOINT_URL = "/devicecode"; + 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 SystemScopeService scopeService; @@ -132,7 +129,7 @@ public class DeviceEndpoint { private final OAuth2RequestFactory oAuth2RequestFactory; private final PerunOidcConfig perunOidcConfig; private final WebHtmlClasses htmlClasses; - private final PerunScopeClaimTranslationService scopeClaimTranslationService; + private final ScopeClaimTranslationService scopeClaimTranslationService; private final UserInfoService userInfoService; @Autowired @@ -142,9 +139,9 @@ public class DeviceEndpoint { OAuth2RequestFactory oAuth2RequestFactory, PerunOidcConfig perunOidcConfig, WebHtmlClasses htmlClasses, - PerunScopeClaimTranslationService scopeClaimTranslationService, - UserInfoService userInfoService) { - + ScopeClaimTranslationService scopeClaimTranslationService, + UserInfoService userInfoService) + { this.clientService = clientService; this.scopeService = scopeService; this.deviceCodeService = deviceCodeService; @@ -155,20 +152,16 @@ public class DeviceEndpoint { this.userInfoService = userInfoService; } - @RequestMapping( - value = "/" + URL, - method = RequestMethod.POST, - consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, - produces = MediaType.APPLICATION_JSON_VALUE - ) + @PostMapping(value = ENDPOINT_URL, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, + produces = MediaType.APPLICATION_JSON_VALUE) public String requestDeviceCode(@RequestParam(CLIENT_ID) String clientId, @RequestParam(name = SCOPE, required = false) String scope, @RequestParam(name = ACR_VALUES, required = false) String acrValues, Map parameters, - ModelMap model) { - + ModelMap model) + { ClientWithScopes clientWithScopes = new ClientWithScopes(); - String errorViewName = handleRequest(clientId, clientWithScopes, scope, model); + String errorViewName = preprocessRequest(clientId, clientWithScopes, scope, model); if (errorViewName != null) { return errorViewName; @@ -186,78 +179,56 @@ public class DeviceEndpoint { response.put(DEVICE_CODE, dc.getDeviceCode()); response.put(USER_CODE, dc.getUserCode()); + Map uriParams = new HashMap<>(); if (StringUtils.hasText(acrValues)) { - response.put( - VERIFICATION_URI, - constructURI(perunOidcConfig.getConfigBean().getIssuer() + USER_URL, Map.of(ACR_VALUES, acrValues)) - ); - } else { - response.put(VERIFICATION_URI, perunOidcConfig.getConfigBean().getIssuer() + USER_URL); + uriParams.put(ACR_VALUES, acrValues); + } + String uriBase = perunOidcConfig.getConfigBean().getIssuer(false) + REQUEST_USER_CODE_URL; + response.put(VERIFICATION_URI, constructVerificationURI(uriBase, uriParams)); + + if (perunOidcConfig.getConfigBean().isAllowCompleteDeviceCodeUri()) { + uriParams.put(USER_CODE, dc.getUserCode()); + response.put(VERIFICATION_URI_COMPLETE, constructVerificationURI(uriBase, uriParams)); } if (clientWithScopes.getClient().getDeviceCodeValiditySeconds() != null) { 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); return JsonEntityView.VIEWNAME; - } catch (DeviceCodeCreationException dcce) { + } catch (DeviceCodeCreationException ex) { model.put(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); - model.put(JsonErrorView.ERROR, dcce.getError()); - model.put(JsonErrorView.ERROR_MESSAGE, dcce.getMessage()); - + model.put(JsonErrorView.ERROR, ex.getError()); + model.put(JsonErrorView.ERROR_MESSAGE, ex.getMessage()); return JsonErrorView.VIEWNAME; - } catch (URISyntaxException use) { + } catch (URISyntaxException ex) { log.error("unable to build verification_uri_complete due to wrong syntax of uri components"); model.put(HttpCodeView.CODE, HttpStatus.INTERNAL_SERVER_ERROR); - return HttpCodeView.VIEWNAME; } } @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, - @ModelAttribute(AUTHORIZATION_REQUEST) AuthorizationRequest authRequest, - Principal p, HttpServletRequest req, - ModelMap model, - HttpSession session) { - - if (!perunOidcConfig.getConfigBean().isAllowCompleteDeviceCodeUri() || userCode == null) { - // 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); + ModelMap model) + { + if (perunOidcConfig.getConfigBean().isAllowCompleteDeviceCodeUri() && StringUtils.hasText(userCode)) { + return verifyUserCode(userCode, req, model); } else { - // complete verification uri was used, we received user code directly - // skip requesting code page - // user must be logged in - return readUserCode(userCode, model, p, req, session); + return getRequestUserCodeViewName(req, model); } } @PreAuthorize("hasRole('ROLE_USER')") - @RequestMapping(value = "/" + USER_URL + "/verify", method = RequestMethod.POST) - public String readUserCode(@RequestParam(USER_CODE) String userCode, - ModelMap model, - Principal p, - HttpServletRequest req, - HttpSession session) { + @PostMapping(value = REQUEST_USER_CODE_URL) + public String verifyUserCode(@RequestParam(value = USER_CODE) String userCode, + HttpServletRequest req, + ModelMap model) + { + model.put(USER_CODE, userCode); DeviceCode dc = deviceCodeService.lookUpByUserCode(userCode); @@ -266,30 +237,49 @@ public class DeviceEndpoint { 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()); model.put(CLIENT, client); model.put(DC, dc); - model.put(SCOPES, getSortedScopes(dc)); + HttpSession session = req.getSession(); AuthorizationRequest authorizationRequest = oAuth2RequestFactory.createAuthorizationRequest(dc.getRequestParameters()); - 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')") - @RequestMapping(value = "/" + USER_URL + "/approve", method = RequestMethod.POST) - public String approveDevice(@RequestParam(USER_CODE) String userCode, - @RequestParam(value = USER_OAUTH_APPROVAL) Boolean approve, - Principal p, - HttpServletRequest req, - ModelMap model, - Authentication auth, - HttpSession session) { - + @PostMapping(value = DEVICE_APPROVED_URL) + public String processApproveDevice(@RequestParam(USER_CODE) String userCode, + @RequestParam(value = USER_OAUTH_APPROVAL) Boolean approve, + Principal p, + HttpServletRequest req, + ModelMap model, + Authentication auth) + { + HttpSession session = req.getSession(); AuthorizationRequest authorizationRequest = (AuthorizationRequest) session.getAttribute(AUTHORIZATION_REQUEST); if (authorizationRequest == null) { throw new IllegalArgumentException("Authorization request not found in the session"); @@ -325,15 +315,13 @@ public class DeviceEndpoint { OAuth2Request o2req = oAuth2RequestFactory.createOAuth2Request(authorizationRequest); 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); - - return DEVICE_APPROVED; + return getDeviceApprovedViewName(req, model); } - private String handleRequest(String clientId, ClientWithScopes clientWithScopes, String scope, ModelMap model) { + private String preprocessRequest(String clientId, ClientWithScopes clientWithScopes, String scope, ModelMap model) { ClientDetailsEntity client; try { @@ -360,10 +348,9 @@ public class DeviceEndpoint { Set allowedScopes = client.getScope(); 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(JsonErrorView.ERROR, INVALID_SCOPE); - return JsonErrorView.VIEWNAME; } @@ -402,18 +389,25 @@ public class DeviceEndpoint { ControllerUtils.setPageOptions(model, req, htmlClasses, perunOidcConfig); model.put(PAGE, REQUEST_USER_CODE); - String shibAuthnContextClass = ""; - - model.put(ACR, shibAuthnContextClass); 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) { if (perunOidcConfig.getTheme().equalsIgnoreCase(DEFAULT)) { + model.put(SCOPES, ControllerUtils.getSortedScopes(dc.getScope(), scopeService)); return APPROVE_DEVICE; } - model.remove(SCOPES); ClientDetailsEntity client = (ClientDetailsEntity) model.get(CLIENT); PerunUserInfo user = (PerunUserInfo) userInfoService.getByUsernameAndClientId( @@ -428,39 +422,15 @@ public class DeviceEndpoint { return THEMED_APPROVE_DEVICE; } - private Set getSortedScopes(DeviceCode dc) { - Set scopes = scopeService.fromStrings(dc.getScope()); - - Set sortedScopes = new LinkedHashSet<>(scopes.size()); - Set 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 params) { - if (params.isEmpty()) { + private String constructVerificationURI(String uri, Map params) throws URISyntaxException { + if (params == null || params.isEmpty()) { return uri; } - StringBuilder uriBuilder = new StringBuilder(uri); - - if (!uri.contains("?")) { - Optional key = params.keySet().stream().findFirst(); - uriBuilder.append("?").append(key).append("=").append(params.get(key)); - params.remove(key); + URIBuilder builder = new URIBuilder(uri); + for (Map.Entry param: params.entrySet()) { + builder.addParameter(param.getKey(), param.getValue()); } - - for (Map.Entry entry : params.entrySet()) { - uriBuilder.append("&").append(entry.getKey()).append("=").append(entry.getValue()); - } - - return uriBuilder.toString(); + return builder.build().toString(); } } diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/OAuthConfirmationController.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/OAuthConfirmationController.java index 71755f1ce..6f646400f 100644 --- a/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/OAuthConfirmationController.java +++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/OAuthConfirmationController.java @@ -166,7 +166,7 @@ public class OAuthConfirmationController { model.put(CLIENT, client); model.put(REDIRECT_URI, authRequest.getRedirectUri()); - Set sortedScopes = getSortedScopes(authRequest); + Set sortedScopes = ControllerUtils.getSortedScopes(authRequest.getScope(), scopeService); model.put(SCOPES, sortedScopes); // get the userinfo claims for each scope @@ -212,26 +212,6 @@ public class OAuthConfirmationController { } } - private Set getSortedScopes(AuthorizationRequest authRequest) { - // pre-process the scopes - Set scopes = scopeService.fromStrings(authRequest.getScope()); - - Set sortedScopes = new LinkedHashSet<>(scopes.size()); - Set 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> getClaimsForScopes(Principal p, Set sortedScopes) { UserInfo user = userInfoService.getByUsername(p.getName()); Map> claimsForScopes = new HashMap<>(); diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/SamlInvalidateSessionFilter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/SamlInvalidateSessionFilter.java index be4a5b02f..ee3651e96 100644 --- a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/SamlInvalidateSessionFilter.java +++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/SamlInvalidateSessionFilter.java @@ -1,5 +1,7 @@ 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 java.io.IOException; @@ -16,25 +18,30 @@ import javax.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; 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.web.filter.GenericFilterBean; @Slf4j 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 List internalReferrers = new ArrayList<>(); - public SamlInvalidateSessionFilter(String pattern, - String idpEntityId, + public SamlInvalidateSessionFilter(String idpEntityId, String oidcIssuer, String proxySpEntityId, SecurityContextLogoutHandler contextLogoutHandler, String[] internalReferrers) { - this.matcher = new AntPathRequestMatcher(pattern); if (StringUtils.hasText(idpEntityId)) { this.internalReferrers.add(idpEntityId); } @@ -60,9 +67,10 @@ public class SamlInvalidateSessionFilter extends GenericFilterBean { { HttpServletRequest req = (HttpServletRequest) request; 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); - if (!isInternalReferer(referer)) { + if (!isInternalReferer(referer, !isDeviceCodeFlow)) { log.debug("Got external referer, clear session to reauthenticate"); contextLogoutHandler.logout(req, res, null); } @@ -70,9 +78,9 @@ public class SamlInvalidateSessionFilter extends GenericFilterBean { 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 - return true; + return emptyRefererAsInternal; } for (String internal : internalReferrers) { if (referer.startsWith(internal)) { diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/CallPerunFiltersFilter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/CallPerunFiltersFilter.java index c5fb1be71..a4ce091d9 100644 --- a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/CallPerunFiltersFilter.java +++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/CallPerunFiltersFilter.java @@ -1,5 +1,8 @@ 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.service.ClientDetailsEntityService; 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.server.adapters.PerunAdapter; import java.io.IOException; +import java.util.Arrays; import java.util.List; import java.util.Properties; import javax.annotation.PostConstruct; @@ -19,6 +23,9 @@ import javax.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; 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.web.filter.GenericFilterBean; @@ -31,6 +38,13 @@ import org.springframework.web.filter.GenericFilterBean; @Slf4j 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 private Properties coreProperties; @@ -60,25 +74,30 @@ public class CallPerunFiltersFilter extends GenericFilterBean { public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { - List filters = perunFiltersContext.getFilters(); - if (filters != null && !filters.isEmpty()) { - HttpServletRequest request = (HttpServletRequest) servletRequest; - ClientDetailsEntity client = FiltersUtils.extractClientFromRequest(request, authRequestFactory, - clientDetailsEntityService); - Facility facility = null; - if (client != null && StringUtils.hasText(client.getClientId())) { - try { - facility = perunAdapter.getFacilityByClientId(client.getClientId()); - } catch (Exception e) { - log.warn("{} - could not fetch facility for client_id '{}'", - CallPerunFiltersFilter.class.getSimpleName(), client.getClientId(), e); + 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 filters = perunFiltersContext.getFilters(); + if (filters != null && !filters.isEmpty()) { + ClientDetailsEntity client = FiltersUtils.extractClientFromRequest(request, authRequestFactory, + clientDetailsEntityService); + Facility facility = null; + if (client != null && StringUtils.hasText(client.getClientId())) { + try { + facility = perunAdapter.getFacilityByClientId(client.getClientId()); + } catch (Exception e) { + log.warn("{} - could not fetch facility for client_id '{}'", + CallPerunFiltersFilter.class.getSimpleName(), client.getClientId(), e); + } } - } - PerunUser user = FiltersUtils.getPerunUser(request, perunAdapter, samlProperties.getUserIdentifierAttribute()); - FilterParams params = new FilterParams(client, facility, user); - for (PerunRequestFilter filter : filters) { - if (!filter.doFilter(servletRequest, servletResponse, params)) { - return; + PerunUser user = FiltersUtils.getPerunUser(request, perunAdapter, + samlProperties.getUserIdentifierAttribute()); + FilterParams params = new FilterParams(client, facility, user); + for (PerunRequestFilter filter : filters) { + if (!filter.doFilter(servletRequest, servletResponse, params)) { + return; + } } } } diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/FiltersUtils.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/FiltersUtils.java index 8392efed9..43964205d 100644 --- a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/FiltersUtils.java +++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/FiltersUtils.java @@ -1,9 +1,10 @@ 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 com.google.common.base.Strings; 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.oidc.models.Facility; 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.HttpServletResponse; 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.providers.ExpiringUsernameAuthenticationToken; 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; /** @@ -37,8 +34,6 @@ import org.springframework.util.StringUtils; @Slf4j 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. * @@ -69,21 +64,29 @@ public class FiltersUtils { OAuth2RequestFactory authRequestFactory, 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; } - AuthorizationRequest authRequest = authRequestFactory.createAuthorizationRequest( - FiltersUtils.createRequestMap(request.getParameterMap())); + String clientId; + 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; - if (Strings.isNullOrEmpty(authRequest.getClientId())) { + if (!StringUtils.hasText(clientId)) { log.debug("cannot extract client - ClientID is null or empty"); return null; } - client = clientService.loadClientByClientId(authRequest.getClientId()); - if (Strings.isNullOrEmpty(client.getClientName())) { + client = clientService.loadClientByClientId(clientId); + if (!StringUtils.hasText(client.getClientName())) { log.warn("cannot extract clientName for the clientID '{}'", client.getClientId()); return null; } @@ -207,11 +210,11 @@ public class FiltersUtils { /** * Redirect user to the unapproved page. - * @param request original request object + * @param base Base URL * @param response response object * @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 Map params = new HashMap<>(); @@ -219,8 +222,7 @@ public class FiltersUtils { params.put("client_id", clientId); } - String redirectUrl = ControllerUtils.createRedirectUrl(request, PerunFilterConstants.AUTHORIZE_REQ_PATTERN, - redirectMapping, params); + String redirectUrl = ControllerUtils.createRedirectUrl(base, redirectMapping, params); response.reset(); response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); 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. - * @param request Request object + * @param base base URL * @param response Response object * @param facility Facility representing the client * @param user User accessing the service @@ -237,7 +239,7 @@ public class FiltersUtils { * @param facilityAttributes Actual facility attributes * @param perunAdapter Adapter to call Perun */ - public static void redirectUserCannotAccess(HttpServletRequest request, + public static void redirectUserCannotAccess(String base, HttpServletResponse response, Facility facility, PerunUser user, @@ -263,7 +265,7 @@ public class FiltersUtils { if (facilityAttributes.get(facilityAttrsConfig.getDynamicRegistrationAttr()).valueAsBoolean()) { // redirect to registration form - FiltersUtils.redirectToRegistrationForm(request, response, clientIdentifier, facility, user); + FiltersUtils.redirectToRegistrationForm(base, response, clientIdentifier, facility, user); return; } } @@ -271,7 +273,7 @@ public class FiltersUtils { // cannot register, redirect to unapproved 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, @@ -286,13 +288,13 @@ public class FiltersUtils { return filled; } - private static void redirectToRegistrationForm(HttpServletRequest request, HttpServletResponse response, + private static void redirectToRegistrationForm(String base, HttpServletResponse response, String clientIdentifier, Facility facility, PerunUser user) { Map params = new HashMap<>(); params.put("client_id", clientIdentifier); params.put("facility_id", facility.getId().toString()); 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); log.debug("redirecting user '{}' to the registration form URL: {}", user, redirectUrl); response.reset(); diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/PerunFilterConstants.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/PerunFilterConstants.java index 8469ed180..23a1f7426 100644 --- a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/PerunFilterConstants.java +++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/PerunFilterConstants.java @@ -12,9 +12,8 @@ import java.util.Map; public class PerunFilterConstants { public static final String AUTHORIZE_REQ_PATTERN = "/authorize"; - public static final String SHIB_IDENTITY_PROVIDER = "Shib-Identity-Provider"; - public static final String SHIB_AUTHN_CONTEXT_CLASS = "Shib-AuthnContext-Class"; - public static final String SHIB_AUTHN_CONTEXT_METHOD = "Shib-Authentication-Method"; + public static final String DEVICE_APPROVE_REQ_PATTERN = "/device/code"; + public static final String DEVICE_CHECK_CODE_REQ_PATTERN = "/device/checkcode"; public static final String PARAM_CLIENT_ID = "client_id"; public static final String PARAM_SCOPE = "scope"; diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/PerunRequestFilter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/PerunRequestFilter.java index 157296e01..3d5503c97 100644 --- a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/PerunRequestFilter.java +++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/PerunRequestFilter.java @@ -1,6 +1,8 @@ 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_APPROVE_REQ_PATTERN; +import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.DEVICE_CHECK_CODE_REQ_PATTERN; import java.io.IOException; import java.util.Arrays; @@ -11,6 +13,7 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.OrRequestMatcher; 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 SUBS = "subs"; - private static final RequestMatcher requestMatcher = new AntPathRequestMatcher(AUTHORIZE_REQ_PATTERN); - private final String filterName; private Set clientIds = new HashSet<>(); private Set subs = new HashSet<>(); @@ -77,11 +78,6 @@ public abstract class PerunRequestFilter { public boolean doFilter(ServletRequest req, ServletResponse res, FilterParams params) throws IOException { 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)) { log.trace("{} - executing filter", filterName); return this.process(req, res, params); diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/PerunAuthorizationFilter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/PerunAuthorizationFilter.java index aa1d22801..f5679b096 100644 --- a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/PerunAuthorizationFilter.java +++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/PerunAuthorizationFilter.java @@ -6,6 +6,7 @@ import cz.muni.ics.oidc.models.PerunAttributeValue; import cz.muni.ics.oidc.models.PerunUser; import cz.muni.ics.oidc.server.adapters.PerunAdapter; 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.FiltersUtils; import cz.muni.ics.oidc.server.filters.PerunRequestFilter; @@ -35,6 +36,7 @@ public class PerunAuthorizationFilter extends PerunRequestFilter { private final PerunAdapter perunAdapter; private final FacilityAttrsConfig facilityAttrsConfig; private final String filterName; + private final PerunOidcConfig config; public PerunAuthorizationFilter(PerunRequestFilterParams params) { super(params); @@ -42,6 +44,7 @@ public class PerunAuthorizationFilter extends PerunRequestFilter { this.perunAdapter = beanUtil.getBean(PerunAdapter.class); this.facilityAttrsConfig = beanUtil.getBean(FacilityAttrsConfig.class); this.filterName = params.getFilterName(); + this.config = beanUtil.getBean(PerunOidcConfig.class); } @Override @@ -81,7 +84,7 @@ public class PerunAuthorizationFilter extends PerunRequestFilter { log.info("{} - user allowed to access the service", filterName); return true; } else { - FiltersUtils.redirectUserCannotAccess(request, response, facility, user, clientIdentifier, + FiltersUtils.redirectUserCannotAccess(config.getConfigBean().getIssuer(), response, facility, user, clientIdentifier, facilityAttrsConfig, facilityAttributes, perunAdapter, PerunUnapprovedController.UNAPPROVED_AUTHORIZATION); return false; diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/PerunIsCesnetEligibleFilter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/PerunIsCesnetEligibleFilter.java index bd51b8ed9..a51236965 100644 --- a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/PerunIsCesnetEligibleFilter.java +++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/PerunIsCesnetEligibleFilter.java @@ -11,6 +11,7 @@ import cz.muni.ics.oidc.BeanUtil; import cz.muni.ics.oidc.models.PerunAttributeValue; import cz.muni.ics.oidc.models.PerunUser; 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.FiltersUtils; import cz.muni.ics.oidc.server.filters.PerunFilterConstants; @@ -58,12 +59,14 @@ public class PerunIsCesnetEligibleFilter extends PerunRequestFilter { private final int validityPeriod; /* END OF CONFIGURATION PROPERTIES */ + private final PerunOidcConfig config; private final PerunAdapter perunAdapter; private final String filterName; public PerunIsCesnetEligibleFilter(PerunRequestFilterParams params) { super(params); BeanUtil beanUtil = params.getBeanUtil(); + this.config = beanUtil.getBean(PerunOidcConfig.class); this.perunAdapter = beanUtil.getBean(PerunAdapter.class); this.isCesnetEligibleAttrName = params.getProperty(IS_CESNET_ELIGIBLE_ATTR_NAME); 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_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); log.debug("{} - redirecting user to unapproved: URL '{}'", filterName, redirectUrl); res.reset(); diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/PerunIsTestSpFilter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/PerunIsTestSpFilter.java index 1802918ea..d027eed43 100644 --- a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/PerunIsTestSpFilter.java +++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/PerunIsTestSpFilter.java @@ -7,6 +7,7 @@ import cz.muni.ics.oidc.BeanUtil; import cz.muni.ics.oidc.models.Facility; import cz.muni.ics.oidc.models.PerunAttributeValue; 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.FiltersUtils; import cz.muni.ics.oidc.server.filters.PerunFilterConstants; @@ -43,6 +44,7 @@ public class PerunIsTestSpFilter extends PerunRequestFilter { private final String isTestSpAttrName; private final PerunAdapter perunAdapter; private final String filterName; + private final PerunOidcConfig config; public PerunIsTestSpFilter(PerunRequestFilterParams params) { super(params); @@ -50,6 +52,7 @@ public class PerunIsTestSpFilter extends PerunRequestFilter { this.perunAdapter = beanUtil.getBean(PerunAdapter.class); this.isTestSpAttrName = params.getProperty(IS_TEST_SP_ATTR_NAME); this.filterName = params.getFilterName(); + this.config = beanUtil.getBean(PerunOidcConfig.class); } @Override @@ -95,7 +98,7 @@ public class PerunIsTestSpFilter extends PerunRequestFilter { Map params = new HashMap<>(); params.put(PARAM_TARGET, targetURL); - String redirectUrl = ControllerUtils.createRedirectUrl(req, PerunFilterConstants.AUTHORIZE_REQ_PATTERN, + String redirectUrl = ControllerUtils.createRedirectUrl(config.getConfigBean().getIssuer(), IsTestSpController.MAPPING, params); log.debug("{} - redirecting user to testSP warning page: {}", filterName, redirectUrl); res.reset(); diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/ValidUserFilter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/ValidUserFilter.java index 408644400..bf05d8c69 100644 --- a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/ValidUserFilter.java +++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/ValidUserFilter.java @@ -6,6 +6,7 @@ import cz.muni.ics.oidc.models.PerunAttributeValue; import cz.muni.ics.oidc.models.PerunUser; import cz.muni.ics.oidc.server.adapters.PerunAdapter; 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.FiltersUtils; import cz.muni.ics.oidc.server.filters.PerunRequestFilter; @@ -66,6 +67,7 @@ public class ValidUserFilter extends PerunRequestFilter { private final PerunAdapter perunAdapter; private final FacilityAttrsConfig facilityAttrsConfig; private final String filterName; + private final PerunOidcConfig config; public ValidUserFilter(PerunRequestFilterParams params) { super(params); @@ -80,6 +82,7 @@ public class ValidUserFilter extends PerunRequestFilter { this.prodEnvGroups = this.getIdsFromParam(params, PROD_ENV_GROUPS); this.prodEnvVos = this.getIdsFromParam(params, PROD_ENV_VOS); this.filterName = params.getFilterName(); + this.config = beanUtil.getBean(PerunOidcConfig.class); } @Override @@ -103,7 +106,7 @@ public class ValidUserFilter extends PerunRequestFilter { 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)) { return false; } @@ -118,7 +121,7 @@ public class ValidUserFilter extends PerunRequestFilter { additionalVos.addAll(testEnvVos); 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)) { return false; } @@ -126,7 +129,7 @@ public class ValidUserFilter extends PerunRequestFilter { additionalVos.addAll(prodEnvVos); 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)) { return false; } @@ -153,7 +156,6 @@ public class ValidUserFilter extends PerunRequestFilter { private boolean checkMemberValidInGroupsAndVos( PerunUser user, Facility facility, - HttpServletRequest request, HttpServletResponse response, FilterParams params, Set vos, @@ -168,8 +170,8 @@ public class ValidUserFilter extends PerunRequestFilter { Map facilityAttributes = perunAdapter.getFacilityAttributeValues( facility, facilityAttrsConfig.getMembershipAttrNames()); - FiltersUtils.redirectUserCannotAccess(request, response, facility, user, params.getClientIdentifier(), - facilityAttrsConfig, facilityAttributes, perunAdapter, redirectUrl); + FiltersUtils.redirectUserCannotAccess(config.getConfigBean().getIssuer(), response, facility, user, + params.getClientIdentifier(), facilityAttrsConfig, facilityAttributes, perunAdapter, redirectUrl); return false; } diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/ControllerUtils.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/ControllerUtils.java index ad6d20de9..b2d0f9fcc 100644 --- a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/ControllerUtils.java +++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/ControllerUtils.java @@ -84,23 +84,24 @@ public class ControllerUtils { /** * Create redirect URL. * - * @param request Request object - * @param removedPart Part of URL to be removed - * @param pathPart What to include as Path - * @param params Map object of parameters + * @param base Base of URL, might include some path already + * @param path What to include as Path + * @param params Map object of parameters * @return Modified redirect URL */ - public static String createRedirectUrl(HttpServletRequest request, String removedPart, - String pathPart, Map params) { - String baseUrl = request.getRequestURL().toString(); - int endIndex = baseUrl.indexOf(removedPart); - if (endIndex > 1) { - baseUrl = baseUrl.substring(0, endIndex); - } - + public static String createRedirectUrl(String base, + String path, + Map params) + { StringBuilder builder = new StringBuilder(); - builder.append(baseUrl); - builder.append(pathPart); + if (!base.endsWith("/")) { + base += '/'; + } + builder.append(base); + if (path.startsWith("/")) { + path = path.substring(1); + } + builder.append(path); if (!params.isEmpty()) { builder.append('?'); for (Map.Entry entry : params.entrySet()) { @@ -292,4 +293,20 @@ public class ControllerUtils { } } + public static Set getSortedScopes(Set requestedScopes, SystemScopeService scopeService) { + Set scopes = scopeService.fromStrings(requestedScopes); + + Set sortedScopes = new LinkedHashSet<>(scopes.size()); + Set systemScopes = scopeService.getAll(); + + for (SystemScope s : systemScopes) { + if (scopes.contains(s)) { + sortedScopes.add(s); + } + } + + sortedScopes.addAll(Sets.difference(scopes, systemScopes)); + return sortedScopes; + } + } diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/PerunUnapprovedRegistrationController.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/PerunUnapprovedRegistrationController.java index 3d99478b8..e1ec8d437 100644 --- a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/PerunUnapprovedRegistrationController.java +++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/PerunUnapprovedRegistrationController.java @@ -91,7 +91,7 @@ public class PerunUnapprovedRegistrationController { log.debug("groupsForReg: {}", groupsForRegistration); 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)); response.reset(); response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/config/ConfigurationPropertiesBean.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/config/ConfigurationPropertiesBean.java index 9021fca32..911305e59 100644 --- a/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/config/ConfigurationPropertiesBean.java +++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/config/ConfigurationPropertiesBean.java @@ -75,6 +75,15 @@ public class ConfigurationPropertiesBean { 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) { issuer = iss; }