refactor: Merged some controllers
parent
5e1419cd45
commit
22527c9996
|
@ -0,0 +1,15 @@
|
||||||
|
package cz.muni.ics.oauth2.model;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class ClientWithScopes {
|
||||||
|
|
||||||
|
private ClientDetailsEntity client;
|
||||||
|
private Set<String> requestedScopes;
|
||||||
|
|
||||||
|
}
|
|
@ -19,24 +19,33 @@ package cz.muni.ics.oauth2.web;
|
||||||
import com.google.common.collect.Sets;
|
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.DeviceCode;
|
import cz.muni.ics.oauth2.model.DeviceCode;
|
||||||
import cz.muni.ics.oauth2.model.SystemScope;
|
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.openid.connect.config.ConfigurationPropertiesBean;
|
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.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.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
|
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.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.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.http.client.utils.URIBuilder;
|
import org.apache.http.client.utils.URIBuilder;
|
||||||
|
@ -46,6 +55,7 @@ import org.springframework.http.MediaType;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
|
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
|
||||||
|
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
|
||||||
import org.springframework.security.oauth2.common.util.OAuth2Utils;
|
import org.springframework.security.oauth2.common.util.OAuth2Utils;
|
||||||
import org.springframework.security.oauth2.provider.AuthorizationRequest;
|
import org.springframework.security.oauth2.provider.AuthorizationRequest;
|
||||||
import org.springframework.security.oauth2.provider.OAuth2Authentication;
|
import org.springframework.security.oauth2.provider.OAuth2Authentication;
|
||||||
|
@ -53,6 +63,8 @@ import org.springframework.security.oauth2.provider.OAuth2Request;
|
||||||
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
|
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.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
@ -63,96 +75,147 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||||
* @see DeviceTokenGranter
|
* @see DeviceTokenGranter
|
||||||
*
|
*
|
||||||
* @author jricher
|
* @author jricher
|
||||||
*
|
* @author Dominik Baranek (baranek@ics.muni.cz)
|
||||||
*/
|
*/
|
||||||
@Controller
|
@Controller
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class DeviceEndpoint {
|
public class DeviceEndpoint {
|
||||||
|
|
||||||
|
// views
|
||||||
|
public static final String REQUEST_USER_CODE = "requestUserCode";
|
||||||
|
public static final String THEMED_REQUEST_USER_CODE = "themedRequestUserCode";
|
||||||
|
public static final String APPROVE_DEVICE = "approveDevice";
|
||||||
|
public static final String THEMED_APPROVE_DEVICE = "themedApproveDevice";
|
||||||
|
public static final String DEVICE_APPROVED = "deviceApproved";
|
||||||
|
|
||||||
|
// response keys
|
||||||
|
public static final String DEVICE_CODE = "device_code";
|
||||||
|
public static final String USER_CODE = "user_code";
|
||||||
|
public static final String VERIFICATION_URI_COMPLETE = "verification_uri_complete";
|
||||||
|
public static final String EXPIRES_IN = "expires_in";
|
||||||
|
public static final String VERIFICATION_URI = "verification_uri";
|
||||||
|
|
||||||
|
// request params
|
||||||
|
public static final String CLIENT_ID = "client_id";
|
||||||
|
public static final String USER_OAUTH_APPROVAL = "user_oauth_approval";
|
||||||
|
public static final String SCOPE = "scope";
|
||||||
|
public static final String ACR_VALUES = "acr_values";
|
||||||
|
|
||||||
|
// model keys
|
||||||
|
public static final String CLIENT = "client";
|
||||||
|
public static final String DC = "dc";
|
||||||
|
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
|
||||||
|
public static final String DEVICE_CODE_SESSION_ATTRIBUTE = "deviceCode";
|
||||||
|
public static final String AUTHORIZATION_REQUEST = "authorizationRequest";
|
||||||
|
|
||||||
|
// errors
|
||||||
|
public static final String NO_USER_CODE = "noUserCode";
|
||||||
|
public static final String EXPIRED_USER_CODE = "expiredUserCode";
|
||||||
|
public static final String USER_CODE_ALREADY_APPROVED = "userCodeAlreadyApproved";
|
||||||
|
public static final String USER_CODE_MISMATCH = "userCodeMismatch";
|
||||||
|
public static final String INVALID_SCOPE = "invalidScope";
|
||||||
|
|
||||||
|
// other
|
||||||
|
public static final String DEFAULT = "default";
|
||||||
public static final String URL = "devicecode";
|
public static final String URL = "devicecode";
|
||||||
public static final String USER_URL = "device";
|
public static final String USER_URL = "device";
|
||||||
|
|
||||||
@Autowired
|
private final ClientDetailsEntityService clientService;
|
||||||
private ClientDetailsEntityService clientService;
|
private final SystemScopeService scopeService;
|
||||||
|
private final DeviceCodeService deviceCodeService;
|
||||||
|
private final OAuth2RequestFactory oAuth2RequestFactory;
|
||||||
|
private final PerunOidcConfig perunOidcConfig;
|
||||||
|
private final WebHtmlClasses htmlClasses;
|
||||||
|
private final PerunScopeClaimTranslationService scopeClaimTranslationService;
|
||||||
|
private final UserInfoService userInfoService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private SystemScopeService scopeService;
|
public DeviceEndpoint(ClientDetailsEntityService clientService,
|
||||||
|
SystemScopeService scopeService,
|
||||||
|
DeviceCodeService deviceCodeService,
|
||||||
|
OAuth2RequestFactory oAuth2RequestFactory,
|
||||||
|
PerunOidcConfig perunOidcConfig,
|
||||||
|
WebHtmlClasses htmlClasses,
|
||||||
|
PerunScopeClaimTranslationService scopeClaimTranslationService,
|
||||||
|
UserInfoService userInfoService) {
|
||||||
|
|
||||||
@Autowired
|
this.clientService = clientService;
|
||||||
private ConfigurationPropertiesBean config;
|
this.scopeService = scopeService;
|
||||||
|
this.deviceCodeService = deviceCodeService;
|
||||||
|
this.oAuth2RequestFactory = oAuth2RequestFactory;
|
||||||
|
this.perunOidcConfig = perunOidcConfig;
|
||||||
|
this.htmlClasses = htmlClasses;
|
||||||
|
this.scopeClaimTranslationService = scopeClaimTranslationService;
|
||||||
|
this.userInfoService = userInfoService;
|
||||||
|
}
|
||||||
|
|
||||||
@Autowired
|
@RequestMapping(
|
||||||
private DeviceCodeService deviceCodeService;
|
value = "/" + URL,
|
||||||
|
method = RequestMethod.POST,
|
||||||
|
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<String, String> parameters,
|
||||||
|
ModelMap model) {
|
||||||
|
|
||||||
@Autowired
|
ClientWithScopes clientWithScopes = new ClientWithScopes();
|
||||||
private OAuth2RequestFactory oAuth2RequestFactory;
|
String errorViewName = handleRequest(clientId, clientWithScopes, scope, model);
|
||||||
|
|
||||||
@RequestMapping(value = "/" + URL, method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
if (errorViewName != null) {
|
||||||
public String requestDeviceCode(@RequestParam("client_id") String clientId, @RequestParam(name="scope", required=false) String scope, Map<String, String> parameters, ModelMap model) {
|
return errorViewName;
|
||||||
|
|
||||||
ClientDetailsEntity client;
|
|
||||||
try {
|
|
||||||
client = clientService.loadClientByClientId(clientId);
|
|
||||||
|
|
||||||
// make sure this client can do the device flow
|
|
||||||
|
|
||||||
Collection<String> authorizedGrantTypes = client.getAuthorizedGrantTypes();
|
|
||||||
if (authorizedGrantTypes != null && !authorizedGrantTypes.isEmpty()
|
|
||||||
&& !authorizedGrantTypes.contains(DeviceTokenGranter.GRANT_TYPE)) {
|
|
||||||
throw new InvalidClientException("Unauthorized grant type: " + DeviceTokenGranter.GRANT_TYPE);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
log.error("IllegalArgumentException was thrown when attempting to load client", e);
|
|
||||||
model.put(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
|
|
||||||
return HttpCodeView.VIEWNAME;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client == null) {
|
|
||||||
log.error("could not find client " + clientId);
|
|
||||||
model.put(HttpCodeView.CODE, HttpStatus.NOT_FOUND);
|
|
||||||
return HttpCodeView.VIEWNAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure the client is allowed to ask for those scopes
|
|
||||||
Set<String> requestedScopes = OAuth2Utils.parseParameterList(scope);
|
|
||||||
Set<String> allowedScopes = client.getScope();
|
|
||||||
|
|
||||||
if (!scopeService.scopesMatch(allowedScopes, requestedScopes)) {
|
|
||||||
// client asked for scopes it can't have
|
|
||||||
log.error("Client asked for " + requestedScopes + " but is allowed " + allowedScopes);
|
|
||||||
model.put(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
|
|
||||||
model.put(JsonErrorView.ERROR, "invalid_scope");
|
|
||||||
return JsonErrorView.VIEWNAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we got here the request is legit
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
DeviceCode dc = deviceCodeService.createNewDeviceCode(requestedScopes, client, parameters);
|
DeviceCode dc = deviceCodeService.createNewDeviceCode(
|
||||||
|
clientWithScopes.getRequestedScopes(),
|
||||||
|
clientWithScopes.getClient(),
|
||||||
|
parameters
|
||||||
|
);
|
||||||
|
|
||||||
Map<String, Object> response = new HashMap<>();
|
Map<String, Object> response = new HashMap<>();
|
||||||
response.put("device_code", dc.getDeviceCode());
|
|
||||||
response.put("user_code", dc.getUserCode());
|
response.put(DEVICE_CODE, dc.getDeviceCode());
|
||||||
response.put("verification_uri", config.getIssuer() + USER_URL);
|
response.put(USER_CODE, dc.getUserCode());
|
||||||
if (client.getDeviceCodeValiditySeconds() != null) {
|
|
||||||
response.put("expires_in", client.getDeviceCodeValiditySeconds());
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clientWithScopes.getClient().getDeviceCodeValiditySeconds() != null) {
|
||||||
|
response.put(EXPIRES_IN, clientWithScopes.getClient().getDeviceCodeValiditySeconds());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.isAllowCompleteDeviceCodeUri()) {
|
if (perunOidcConfig.getConfigBean().isAllowCompleteDeviceCodeUri()) {
|
||||||
URI verificationUriComplete = new URIBuilder(config.getIssuer() + USER_URL)
|
URI verificationUriComplete = new URIBuilder(perunOidcConfig.getConfigBean().getIssuer() + USER_URL)
|
||||||
.addParameter("user_code", dc.getUserCode())
|
.addParameter(USER_CODE, dc.getUserCode())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
response.put("verification_uri_complete", verificationUriComplete.toString());
|
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 dcce) {
|
||||||
|
|
||||||
model.put(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
|
model.put(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
|
||||||
model.put(JsonErrorView.ERROR, dcce.getError());
|
model.put(JsonErrorView.ERROR, dcce.getError());
|
||||||
model.put(JsonErrorView.ERROR_MESSAGE, dcce.getMessage());
|
model.put(JsonErrorView.ERROR_MESSAGE, dcce.getMessage());
|
||||||
|
@ -164,111 +227,98 @@ public class DeviceEndpoint {
|
||||||
|
|
||||||
return HttpCodeView.VIEWNAME;
|
return HttpCodeView.VIEWNAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreAuthorize("hasRole('ROLE_USER')")
|
@PreAuthorize("hasRole('ROLE_USER')")
|
||||||
@RequestMapping(value = "/" + USER_URL, method = RequestMethod.GET)
|
@RequestMapping(value = "/" + USER_URL, method = RequestMethod.GET)
|
||||||
public String requestUserCode(@RequestParam(value = "user_code", required = false) String userCode, ModelMap model, HttpSession session) {
|
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 (!config.isAllowCompleteDeviceCodeUri() || userCode == null) {
|
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,
|
// 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
|
// print out a page that asks the user to enter their user code
|
||||||
// user must be logged in
|
// user must be logged in
|
||||||
return "requestUserCode";
|
return getRequestUserCodeViewName(req, model);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// complete verification uri was used, we received user code directly
|
// complete verification uri was used, we received user code directly
|
||||||
// skip requesting code page
|
// skip requesting code page
|
||||||
// user must be logged in
|
// user must be logged in
|
||||||
return readUserCode(userCode, model, session);
|
return readUserCode(userCode, model, p, req, session);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreAuthorize("hasRole('ROLE_USER')")
|
@PreAuthorize("hasRole('ROLE_USER')")
|
||||||
@RequestMapping(value = "/" + USER_URL + "/verify", method = RequestMethod.POST)
|
@RequestMapping(value = "/" + USER_URL + "/verify", method = RequestMethod.POST)
|
||||||
public String readUserCode(@RequestParam("user_code") String userCode, ModelMap model, HttpSession session) {
|
public String readUserCode(@RequestParam(USER_CODE) String userCode,
|
||||||
|
ModelMap model,
|
||||||
|
Principal p,
|
||||||
|
HttpServletRequest req,
|
||||||
|
HttpSession session) {
|
||||||
|
|
||||||
// look up the request based on the user code
|
|
||||||
DeviceCode dc = deviceCodeService.lookUpByUserCode(userCode);
|
DeviceCode dc = deviceCodeService.lookUpByUserCode(userCode);
|
||||||
|
|
||||||
// we couldn't find the device code
|
String errorViewName = checkDeviceCodeIsValid(dc, req, model);
|
||||||
if (dc == null) {
|
if (errorViewName != null) {
|
||||||
model.addAttribute("error", "noUserCode");
|
return errorViewName;
|
||||||
return "requestUserCode";
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure the code hasn't expired yet
|
|
||||||
if (dc.getExpiration() != null && dc.getExpiration().before(new Date())) {
|
|
||||||
model.addAttribute("error", "expiredUserCode");
|
|
||||||
return "requestUserCode";
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure the device code hasn't already been approved
|
|
||||||
if (dc.isApproved()) {
|
|
||||||
model.addAttribute("error", "userCodeAlreadyApproved");
|
|
||||||
return "requestUserCode";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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));
|
||||||
// pre-process the scopes
|
|
||||||
Set<SystemScope> scopes = scopeService.fromStrings(dc.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));
|
|
||||||
|
|
||||||
model.put("scopes", sortedScopes);
|
|
||||||
|
|
||||||
AuthorizationRequest authorizationRequest = oAuth2RequestFactory.createAuthorizationRequest(dc.getRequestParameters());
|
AuthorizationRequest authorizationRequest = oAuth2RequestFactory.createAuthorizationRequest(dc.getRequestParameters());
|
||||||
|
|
||||||
session.setAttribute("authorizationRequest", authorizationRequest);
|
session.setAttribute(AUTHORIZATION_REQUEST, authorizationRequest);
|
||||||
session.setAttribute("deviceCode", dc);
|
session.setAttribute(DEVICE_CODE_SESSION_ATTRIBUTE, dc);
|
||||||
|
|
||||||
return "approveDevice";
|
return getApproveDeviceViewName(model, p, req, (DeviceCode) model.get(DC));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreAuthorize("hasRole('ROLE_USER')")
|
@PreAuthorize("hasRole('ROLE_USER')")
|
||||||
@RequestMapping(value = "/" + USER_URL + "/approve", method = RequestMethod.POST)
|
@RequestMapping(value = "/" + USER_URL + "/approve", method = RequestMethod.POST)
|
||||||
public String approveDevice(@RequestParam("user_code") String userCode,
|
public String approveDevice(@RequestParam(USER_CODE) String userCode,
|
||||||
@RequestParam(value = "user_oauth_approval") Boolean approve,
|
@RequestParam(value = USER_OAUTH_APPROVAL) Boolean approve,
|
||||||
ModelMap model, Authentication auth, HttpSession session) {
|
Principal p,
|
||||||
AuthorizationRequest authorizationRequest = (AuthorizationRequest) session.getAttribute("authorizationRequest");
|
HttpServletRequest req,
|
||||||
DeviceCode dc = (DeviceCode) session.getAttribute("deviceCode");
|
ModelMap model,
|
||||||
|
Authentication auth,
|
||||||
|
HttpSession session) {
|
||||||
|
|
||||||
|
AuthorizationRequest authorizationRequest = (AuthorizationRequest) session.getAttribute(AUTHORIZATION_REQUEST);
|
||||||
|
if (authorizationRequest == null) {
|
||||||
|
throw new IllegalArgumentException("Authorization request not found in the session");
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceCode dc = (DeviceCode) session.getAttribute(DEVICE_CODE_SESSION_ATTRIBUTE);
|
||||||
|
if (dc == null) {
|
||||||
|
throw new IllegalArgumentException("Device code not found in the session");
|
||||||
|
}
|
||||||
|
|
||||||
// make sure the form that was submitted is the one that we were expecting
|
// make sure the form that was submitted is the one that we were expecting
|
||||||
if (!dc.getUserCode().equals(userCode)) {
|
if (!dc.getUserCode().equals(userCode)) {
|
||||||
model.addAttribute("error", "userCodeMismatch");
|
model.addAttribute(ERROR, USER_CODE_MISMATCH);
|
||||||
return "requestUserCode";
|
return REQUEST_USER_CODE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure the code hasn't expired yet
|
// make sure the code hasn't expired yet
|
||||||
if (dc.getExpiration() != null && dc.getExpiration().before(new Date())) {
|
if (dc.getExpiration() != null && dc.getExpiration().before(new Date())) {
|
||||||
model.addAttribute("error", "expiredUserCode");
|
model.addAttribute(ERROR, EXPIRED_USER_CODE);
|
||||||
return "requestUserCode";
|
return REQUEST_USER_CODE;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientDetailsEntity client = clientService.loadClientByClientId(dc.getClientId());
|
ClientDetailsEntity client = clientService.loadClientByClientId(dc.getClientId());
|
||||||
|
model.put(CLIENT, client);
|
||||||
model.put("client", client);
|
|
||||||
|
|
||||||
// user did not approve
|
// user did not approve
|
||||||
if (!approve) {
|
if (!approve) {
|
||||||
model.addAttribute("approved", false);
|
model.addAttribute(APPROVED, false);
|
||||||
return "deviceApproved";
|
return getApproveDeviceViewName(model, p, req, dc);
|
||||||
}
|
}
|
||||||
|
|
||||||
// create an OAuth request for storage
|
// create an OAuth request for storage
|
||||||
|
@ -276,26 +326,141 @@ public class DeviceEndpoint {
|
||||||
OAuth2Authentication o2Auth = new OAuth2Authentication(o2req, auth);
|
OAuth2Authentication o2Auth = new OAuth2Authentication(o2req, auth);
|
||||||
|
|
||||||
DeviceCode approvedCode = deviceCodeService.approveDeviceCode(dc, o2Auth);
|
DeviceCode approvedCode = deviceCodeService.approveDeviceCode(dc, o2Auth);
|
||||||
|
|
||||||
// pre-process the scopes
|
model.put(SCOPES, getSortedScopes(dc));
|
||||||
|
model.put(APPROVED, true);
|
||||||
|
|
||||||
|
return DEVICE_APPROVED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String handleRequest(String clientId, ClientWithScopes clientWithScopes, String scope, ModelMap model) {
|
||||||
|
ClientDetailsEntity client;
|
||||||
|
|
||||||
|
try {
|
||||||
|
client = clientService.loadClientByClientId(clientId);
|
||||||
|
Collection<String> authorizedGrantTypes = client.getAuthorizedGrantTypes();
|
||||||
|
|
||||||
|
if (authorizedGrantTypes != null
|
||||||
|
&& !authorizedGrantTypes.isEmpty()
|
||||||
|
&& !authorizedGrantTypes.contains(DeviceTokenGranter.GRANT_TYPE)) {
|
||||||
|
throw new InvalidClientException("Unauthorized grant type: " + DeviceTokenGranter.GRANT_TYPE);
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
log.error("IllegalArgumentException was thrown when attempting to load client", e);
|
||||||
|
model.put(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
|
||||||
|
return HttpCodeView.VIEWNAME;
|
||||||
|
} catch (OAuth2Exception e) {
|
||||||
|
log.error("could not find client {}", clientId);
|
||||||
|
model.put(HttpCodeView.CODE, HttpStatus.NOT_FOUND);
|
||||||
|
return HttpCodeView.VIEWNAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure the client is allowed to ask for those scopes
|
||||||
|
Set<String> requestedScopes = OAuth2Utils.parseParameterList(scope);
|
||||||
|
Set<String> allowedScopes = client.getScope();
|
||||||
|
|
||||||
|
if (!scopeService.scopesMatch(allowedScopes, requestedScopes)) {
|
||||||
|
log.error("Client asked for {} but is allowed {}", requestedScopes, allowedScopes);
|
||||||
|
model.put(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
|
||||||
|
model.put(JsonErrorView.ERROR, INVALID_SCOPE);
|
||||||
|
|
||||||
|
return JsonErrorView.VIEWNAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
clientWithScopes.setClient(client);
|
||||||
|
clientWithScopes.setRequestedScopes(requestedScopes);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String checkDeviceCodeIsValid(DeviceCode dc, HttpServletRequest req, ModelMap model) {
|
||||||
|
// we couldn't find the device code
|
||||||
|
if (dc == null) {
|
||||||
|
model.addAttribute(ERROR, NO_USER_CODE);
|
||||||
|
return getRequestUserCodeViewName(req, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure the code hasn't expired yet
|
||||||
|
if (dc.getExpiration() != null && dc.getExpiration().before(new Date())) {
|
||||||
|
model.addAttribute(ERROR, EXPIRED_USER_CODE);
|
||||||
|
return getRequestUserCodeViewName(req, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure the device code hasn't already been approved
|
||||||
|
if (dc.isApproved()) {
|
||||||
|
model.addAttribute(ERROR, USER_CODE_ALREADY_APPROVED);
|
||||||
|
return getRequestUserCodeViewName(req, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getRequestUserCodeViewName(HttpServletRequest req, ModelMap model) {
|
||||||
|
if (perunOidcConfig.getTheme().equalsIgnoreCase(DEFAULT)) {
|
||||||
|
return REQUEST_USER_CODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 getApproveDeviceViewName(ModelMap model, Principal p, HttpServletRequest req, DeviceCode dc) {
|
||||||
|
if (perunOidcConfig.getTheme().equalsIgnoreCase(DEFAULT)) {
|
||||||
|
return APPROVE_DEVICE;
|
||||||
|
}
|
||||||
|
|
||||||
|
model.remove(SCOPES);
|
||||||
|
ClientDetailsEntity client = (ClientDetailsEntity) model.get(CLIENT);
|
||||||
|
|
||||||
|
PerunUserInfo user = (PerunUserInfo) userInfoService.getByUsernameAndClientId(
|
||||||
|
p.getName(),
|
||||||
|
client.getClientId()
|
||||||
|
);
|
||||||
|
|
||||||
|
ControllerUtils.setScopesAndClaims(scopeService, scopeClaimTranslationService, model, dc.getScope(), user);
|
||||||
|
ControllerUtils.setPageOptions(model, req, htmlClasses, perunOidcConfig);
|
||||||
|
|
||||||
|
model.put(PAGE, APPROVE_DEVICE);
|
||||||
|
return THEMED_APPROVE_DEVICE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<SystemScope> getSortedScopes(DeviceCode dc) {
|
||||||
Set<SystemScope> scopes = scopeService.fromStrings(dc.getScope());
|
Set<SystemScope> scopes = scopeService.fromStrings(dc.getScope());
|
||||||
|
|
||||||
Set<SystemScope> sortedScopes = new LinkedHashSet<>(scopes.size());
|
Set<SystemScope> sortedScopes = new LinkedHashSet<>(scopes.size());
|
||||||
Set<SystemScope> systemScopes = scopeService.getAll();
|
Set<SystemScope> systemScopes = scopeService.getAll();
|
||||||
|
|
||||||
// sort scopes for display based on the inherent order of system scopes
|
|
||||||
for (SystemScope s : systemScopes) {
|
for (SystemScope s : systemScopes) {
|
||||||
if (scopes.contains(s)) {
|
if (scopes.contains(s)) {
|
||||||
sortedScopes.add(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));
|
sortedScopes.addAll(Sets.difference(scopes, systemScopes));
|
||||||
|
return sortedScopes;
|
||||||
|
}
|
||||||
|
|
||||||
model.put("scopes", sortedScopes);
|
private String constructURI(String uri, Map<String, String> params) {
|
||||||
model.put("approved", true);
|
if (params.isEmpty()) {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
return "deviceApproved";
|
StringBuilder uriBuilder = new StringBuilder(uri);
|
||||||
|
|
||||||
|
if (!uri.contains("?")) {
|
||||||
|
Optional<String> key = params.keySet().stream().findFirst();
|
||||||
|
uriBuilder.append("?").append(key).append("=").append(params.get(key));
|
||||||
|
params.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Map.Entry<String, String> entry : params.entrySet()) {
|
||||||
|
uriBuilder.append("&").append(entry.getKey()).append("=").append(entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
return uriBuilder.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,10 @@ import cz.muni.ics.oauth2.model.ClientDetailsEntity;
|
||||||
import cz.muni.ics.oauth2.model.SystemScope;
|
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.SystemScopeService;
|
import cz.muni.ics.oauth2.service.SystemScopeService;
|
||||||
|
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.model.UserInfo;
|
import cz.muni.ics.openid.connect.model.UserInfo;
|
||||||
import cz.muni.ics.openid.connect.request.ConnectRequestParameters;
|
import cz.muni.ics.openid.connect.request.ConnectRequestParameters;
|
||||||
import cz.muni.ics.openid.connect.service.ScopeClaimTranslationService;
|
import cz.muni.ics.openid.connect.service.ScopeClaimTranslationService;
|
||||||
|
@ -41,6 +45,9 @@ import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.http.client.utils.URIBuilder;
|
import org.apache.http.client.utils.URIBuilder;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
@ -53,6 +60,8 @@ import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.SessionAttributes;
|
import org.springframework.web.bind.annotation.SessionAttributes;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author jricher
|
* @author jricher
|
||||||
*
|
*
|
||||||
|
@ -62,20 +71,54 @@ import org.springframework.web.bind.annotation.SessionAttributes;
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class OAuthConfirmationController {
|
public class OAuthConfirmationController {
|
||||||
|
|
||||||
@Autowired
|
public static final String AUTHORIZATION_REQUEST = "authorizationRequest";
|
||||||
|
public static final String ERROR = "error";
|
||||||
|
public static final String INTERACTION_REQUIRED = "interaction_required";
|
||||||
|
public static final String STATE = "state";
|
||||||
|
public static final String NONE = "none";
|
||||||
|
public static final String REDIRECT = "redirect";
|
||||||
|
public static final String CODE = "code";
|
||||||
|
public static final String AUTH_REQUEST = "auth_request";
|
||||||
|
public static final String CLIENT = "client";
|
||||||
|
public static final String REDIRECT_URI = "redirect_uri";
|
||||||
|
public static final String SCOPES = "scopes";
|
||||||
|
public static final String CLAIMS = "claims";
|
||||||
|
public static final String CONTACTS = "contacts";
|
||||||
|
public static final String GRAS = "gras";
|
||||||
|
public static final String DEFAULT = "default";
|
||||||
|
public static final String PAGE = "page";
|
||||||
|
public static final String CONSENT = "consent";
|
||||||
|
public static final String THEMED_APPROVE = "themedApprove";
|
||||||
|
public static final String APPROVE = "approve";
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
private ClientDetailsEntityService clientService;
|
private ClientDetailsEntityService clientService;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private SystemScopeService scopeService;
|
private SystemScopeService scopeService;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ScopeClaimTranslationService scopeClaimTranslationService;
|
private ScopeClaimTranslationService scopeClaimTranslationService;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private UserInfoService userInfoService;
|
private UserInfoService userInfoService;
|
||||||
|
private RedirectResolver redirectResolver;
|
||||||
|
private PerunOidcConfig perunOidcConfig;
|
||||||
|
private WebHtmlClasses htmlClasses;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private RedirectResolver redirectResolver;
|
public OAuthConfirmationController(ClientDetailsEntityService clientService,
|
||||||
|
SystemScopeService scopeService,
|
||||||
|
ScopeClaimTranslationService scopeClaimTranslationService,
|
||||||
|
UserInfoService userInfoService,
|
||||||
|
RedirectResolver redirectResolver,
|
||||||
|
PerunOidcConfig perunOidcConfig,
|
||||||
|
WebHtmlClasses htmlClasses) {
|
||||||
|
|
||||||
|
this.clientService = clientService;
|
||||||
|
this.scopeService = scopeService;
|
||||||
|
this.scopeClaimTranslationService = scopeClaimTranslationService;
|
||||||
|
this.userInfoService = userInfoService;
|
||||||
|
this.redirectResolver = redirectResolver;
|
||||||
|
this.perunOidcConfig = perunOidcConfig;
|
||||||
|
this.htmlClasses = htmlClasses;
|
||||||
|
}
|
||||||
|
|
||||||
public OAuthConfirmationController() {
|
public OAuthConfirmationController() {
|
||||||
|
|
||||||
|
@ -87,9 +130,9 @@ public class OAuthConfirmationController {
|
||||||
|
|
||||||
@PreAuthorize("hasRole('ROLE_USER')")
|
@PreAuthorize("hasRole('ROLE_USER')")
|
||||||
@RequestMapping("/oauth/confirm_access")
|
@RequestMapping("/oauth/confirm_access")
|
||||||
public String confirmAccess(Map<String, Object> model, Principal p) {
|
public String confirmAccess(Map<String, Object> model, HttpServletRequest req, Principal p) {
|
||||||
|
|
||||||
AuthorizationRequest authRequest = (AuthorizationRequest) model.get("authorizationRequest");
|
AuthorizationRequest authRequest = (AuthorizationRequest) model.get(AUTHORIZATION_REQUEST);
|
||||||
// Check the "prompt" parameter to see if we need to do special processing
|
// Check the "prompt" parameter to see if we need to do special processing
|
||||||
|
|
||||||
String prompt = (String)authRequest.getExtensions().get(ConnectRequestParameters.PROMPT);
|
String prompt = (String)authRequest.getExtensions().get(ConnectRequestParameters.PROMPT);
|
||||||
|
@ -114,36 +157,62 @@ public class OAuthConfirmationController {
|
||||||
return HttpCodeView.VIEWNAME;
|
return HttpCodeView.VIEWNAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prompts.contains("none")) {
|
if (prompts.contains(NONE)) {
|
||||||
// if we've got a redirect URI then we'll send it
|
// if we've got a redirect URI then we'll send it
|
||||||
|
return sendRedirect(authRequest, model, client);
|
||||||
String url = redirectResolver.resolveRedirect(authRequest.getRedirectUri(), client);
|
|
||||||
|
|
||||||
try {
|
|
||||||
URIBuilder uriBuilder = new URIBuilder(url);
|
|
||||||
|
|
||||||
uriBuilder.addParameter("error", "interaction_required");
|
|
||||||
if (!Strings.isNullOrEmpty(authRequest.getState())) {
|
|
||||||
uriBuilder.addParameter("state", authRequest.getState()); // copy the state parameter if one was given
|
|
||||||
}
|
|
||||||
|
|
||||||
return "redirect:" + uriBuilder;
|
|
||||||
|
|
||||||
} catch (URISyntaxException e) {
|
|
||||||
log.error("Can't build redirect URI for prompt=none, sending error instead", e);
|
|
||||||
model.put("code", HttpStatus.FORBIDDEN);
|
|
||||||
return HttpCodeView.VIEWNAME;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model.put("auth_request", authRequest);
|
model.put(AUTH_REQUEST, authRequest);
|
||||||
model.put("client", client);
|
model.put(CLIENT, client);
|
||||||
|
model.put(REDIRECT_URI, authRequest.getRedirectUri());
|
||||||
|
|
||||||
String redirect_uri = authRequest.getRedirectUri();
|
Set<SystemScope> sortedScopes = getSortedScopes(authRequest);
|
||||||
|
model.put(SCOPES, sortedScopes);
|
||||||
|
|
||||||
model.put("redirect_uri", redirect_uri);
|
// get the userinfo claims for each scope
|
||||||
|
model.put(CLAIMS, getClaimsForScopes(p, sortedScopes));
|
||||||
|
|
||||||
|
// contacts
|
||||||
|
if (client.getContacts() != null) {
|
||||||
|
model.put(CONTACTS, Joiner.on(", ").join(client.getContacts()));
|
||||||
|
}
|
||||||
|
model.put(GRAS, true);
|
||||||
|
|
||||||
|
if (perunOidcConfig.getTheme().equalsIgnoreCase(DEFAULT)) {
|
||||||
|
return APPROVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
PerunUserInfo perunUser = (PerunUserInfo) userInfoService.getByUsernameAndClientId(
|
||||||
|
p.getName(), client.getClientId());
|
||||||
|
ControllerUtils.setScopesAndClaims(scopeService, scopeClaimTranslationService, model, authRequest.getScope(),
|
||||||
|
perunUser);
|
||||||
|
ControllerUtils.setPageOptions(model, req, htmlClasses, perunOidcConfig);
|
||||||
|
|
||||||
|
model.put(PAGE, CONSENT);
|
||||||
|
return THEMED_APPROVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String sendRedirect(AuthorizationRequest authRequest, Map<String, Object> model, ClientDetailsEntity client) {
|
||||||
|
String url = redirectResolver.resolveRedirect(authRequest.getRedirectUri(), client);
|
||||||
|
|
||||||
|
try {
|
||||||
|
URIBuilder uriBuilder = new URIBuilder(url);
|
||||||
|
|
||||||
|
uriBuilder.addParameter(ERROR, INTERACTION_REQUIRED);
|
||||||
|
if (!Strings.isNullOrEmpty(authRequest.getState())) {
|
||||||
|
uriBuilder.addParameter(STATE, authRequest.getState()); // copy the state parameter if one was given
|
||||||
|
}
|
||||||
|
|
||||||
|
return REDIRECT + ":" + uriBuilder;
|
||||||
|
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
log.error("Can't build redirect URI for prompt=none, sending error instead", e);
|
||||||
|
model.put(CODE, HttpStatus.FORBIDDEN);
|
||||||
|
return HttpCodeView.VIEWNAME;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<SystemScope> getSortedScopes(AuthorizationRequest authRequest) {
|
||||||
// pre-process the scopes
|
// pre-process the scopes
|
||||||
Set<SystemScope> scopes = scopeService.fromStrings(authRequest.getScope());
|
Set<SystemScope> scopes = scopeService.fromStrings(authRequest.getScope());
|
||||||
|
|
||||||
|
@ -160,11 +229,13 @@ public class OAuthConfirmationController {
|
||||||
// add in any scopes that aren't system scopes to the end of the list
|
// add in any scopes that aren't system scopes to the end of the list
|
||||||
sortedScopes.addAll(Sets.difference(scopes, systemScopes));
|
sortedScopes.addAll(Sets.difference(scopes, systemScopes));
|
||||||
|
|
||||||
model.put("scopes", sortedScopes);
|
return sortedScopes;
|
||||||
|
}
|
||||||
|
|
||||||
// get the userinfo claims for each scope
|
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<>();
|
||||||
|
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
JsonObject userJson = user.toJson();
|
JsonObject userJson = user.toJson();
|
||||||
|
|
||||||
|
@ -183,31 +254,6 @@ public class OAuthConfirmationController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
model.put("claims", claimsForScopes);
|
return claimsForScopes;
|
||||||
|
|
||||||
// contacts
|
|
||||||
if (client.getContacts() != null) {
|
|
||||||
String contacts = Joiner.on(", ").join(client.getContacts());
|
|
||||||
model.put("contacts", contacts);
|
|
||||||
}
|
|
||||||
model.put("gras", true);
|
|
||||||
|
|
||||||
return "approve";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the clientService
|
|
||||||
*/
|
|
||||||
public ClientDetailsEntityService getClientService() {
|
|
||||||
return clientService;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param clientService the clientService to set
|
|
||||||
*/
|
|
||||||
public void setClientService(ClientDetailsEntityService clientService) {
|
|
||||||
this.clientService = clientService;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,189 +0,0 @@
|
||||||
package cz.muni.ics.oidc.web.controllers;
|
|
||||||
|
|
||||||
import cz.muni.ics.oauth2.model.ClientDetailsEntity;
|
|
||||||
import cz.muni.ics.oauth2.model.DeviceCode;
|
|
||||||
import cz.muni.ics.oauth2.service.SystemScopeService;
|
|
||||||
import cz.muni.ics.oauth2.web.DeviceEndpoint;
|
|
||||||
import cz.muni.ics.oidc.server.PerunScopeClaimTranslationService;
|
|
||||||
import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
|
|
||||||
import cz.muni.ics.oidc.server.filters.PerunFilterConstants;
|
|
||||||
import cz.muni.ics.oidc.server.userInfo.PerunUserInfo;
|
|
||||||
import cz.muni.ics.oidc.web.WebHtmlClasses;
|
|
||||||
import cz.muni.ics.openid.connect.service.UserInfoService;
|
|
||||||
import java.security.Principal;
|
|
||||||
import java.util.Map;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpSession;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.oauth2.provider.AuthorizationRequest;
|
|
||||||
import org.springframework.stereotype.Controller;
|
|
||||||
import org.springframework.ui.ModelMap;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
|
|
||||||
@Controller
|
|
||||||
public class ApproveDeviceController {
|
|
||||||
|
|
||||||
public static final String DEVICE = "device";
|
|
||||||
public static final String APPROVE_DEVICE = "approveDevice";
|
|
||||||
public static final String DEVICE_APPROVED = "deviceApproved";
|
|
||||||
public static final String REQUEST_USER_CODE = "requestUserCode";
|
|
||||||
public static final String USER_CODE = "user_code";
|
|
||||||
public static final String USER_OAUTH_APPROVAL = "user_oauth_approval";
|
|
||||||
public static final String URL = "devicecode";
|
|
||||||
public static final String VERIFICATION_URI = "verification_uri";
|
|
||||||
public static final String VERIFICATION_URI_COMPLETE = "verification_uri_complete";
|
|
||||||
public static final String ACR_VALUES = "acr_values";
|
|
||||||
public static final String ENTITY = "entity";
|
|
||||||
public static final String CLIENT_ID = "client_id";
|
|
||||||
public static final String SCOPE = "scope";
|
|
||||||
public static final String ACR = "acr";
|
|
||||||
|
|
||||||
private final SystemScopeService scopeService;
|
|
||||||
private final DeviceEndpoint deviceEndpoint;
|
|
||||||
private final PerunOidcConfig perunOidcConfig;
|
|
||||||
private final WebHtmlClasses htmlClasses;
|
|
||||||
private final PerunScopeClaimTranslationService scopeClaimTranslationService;
|
|
||||||
private final UserInfoService userInfoService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public ApproveDeviceController(SystemScopeService scopeService,
|
|
||||||
DeviceEndpoint deviceEndpoint,
|
|
||||||
PerunOidcConfig perunOidcConfig,
|
|
||||||
WebHtmlClasses htmlClasses,
|
|
||||||
PerunScopeClaimTranslationService scopeClaimTranslationService,
|
|
||||||
UserInfoService userInfoService)
|
|
||||||
{
|
|
||||||
this.scopeService = scopeService;
|
|
||||||
this.deviceEndpoint = deviceEndpoint;
|
|
||||||
this.perunOidcConfig = perunOidcConfig;
|
|
||||||
this.htmlClasses = htmlClasses;
|
|
||||||
this.scopeClaimTranslationService = scopeClaimTranslationService;
|
|
||||||
this.userInfoService = userInfoService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequestMapping(
|
|
||||||
value = {"/" + URL},
|
|
||||||
method = RequestMethod.POST,
|
|
||||||
consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE},
|
|
||||||
produces = {MediaType.APPLICATION_JSON_VALUE},
|
|
||||||
params = {CLIENT_ID, ACR_VALUES}
|
|
||||||
)
|
|
||||||
public String requestDeviceCodeMFA(@RequestParam(CLIENT_ID) String clientId, @RequestParam(name = SCOPE, required = false) String scope,
|
|
||||||
@RequestParam(name = ACR_VALUES) String acrValues, Map<String, String> parameters, ModelMap model)
|
|
||||||
{
|
|
||||||
String result = deviceEndpoint.requestDeviceCode(clientId, scope, parameters, model);
|
|
||||||
|
|
||||||
Map<String, Object> response = (Map<String, Object>) model.get(ENTITY);
|
|
||||||
response.replace(VERIFICATION_URI, response.get(VERIFICATION_URI) + "?" + ACR_VALUES + "=" + acrValues);
|
|
||||||
response.replace(VERIFICATION_URI_COMPLETE, response.get(VERIFICATION_URI_COMPLETE) + "&" + ACR_VALUES + "=" + acrValues);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PreAuthorize("hasRole('ROLE_USER')")
|
|
||||||
@GetMapping(value = "/" + DEVICE,
|
|
||||||
consumes = {"text/html", "application/xhtml+xml","application/xml;q=0.9","image/webp","*/*;q=0.8"})
|
|
||||||
public String requestUserCode(@RequestParam(value = USER_CODE, required = false) String userCode,
|
|
||||||
@ModelAttribute("authorizationRequest") AuthorizationRequest authRequest,
|
|
||||||
Principal p,
|
|
||||||
HttpServletRequest req,
|
|
||||||
ModelMap model,
|
|
||||||
HttpSession session)
|
|
||||||
{
|
|
||||||
String result = deviceEndpoint.requestUserCode(userCode, model, session);
|
|
||||||
if (result.equals(REQUEST_USER_CODE) && !perunOidcConfig.getTheme().equalsIgnoreCase("default")) {
|
|
||||||
ControllerUtils.setPageOptions(model, req, htmlClasses, perunOidcConfig);
|
|
||||||
model.put("page", REQUEST_USER_CODE);
|
|
||||||
String shibAuthnContextClass = "";
|
|
||||||
if (StringUtils.hasText(req.getParameter(ACR_VALUES))) {
|
|
||||||
shibAuthnContextClass = (String) req.getAttribute(PerunFilterConstants.SHIB_AUTHN_CONTEXT_CLASS);
|
|
||||||
if (!StringUtils.hasText(shibAuthnContextClass)) {
|
|
||||||
shibAuthnContextClass = (String) req.getAttribute(PerunFilterConstants.SHIB_AUTHN_CONTEXT_METHOD);
|
|
||||||
}
|
|
||||||
if (!StringUtils.hasText(shibAuthnContextClass)) {
|
|
||||||
shibAuthnContextClass = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
model.put(ACR, shibAuthnContextClass);
|
|
||||||
return "themedRequestUserCode";
|
|
||||||
} else if (result.equals(APPROVE_DEVICE) && !perunOidcConfig.getTheme().equalsIgnoreCase("default")) {
|
|
||||||
return themedApproveDevice(model, p, req);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PreAuthorize("hasRole('ROLE_USER')")
|
|
||||||
@PostMapping(value = "/" + DEVICE + "/verify",
|
|
||||||
consumes = {"text/html", "application/xhtml+xml","application/xml;q=0.9","image/webp","*/*;q=0.8"})
|
|
||||||
public String readUserCode(@RequestParam(USER_CODE) String userCode,
|
|
||||||
@ModelAttribute("authorizationRequest") AuthorizationRequest authRequest,
|
|
||||||
Principal p,
|
|
||||||
HttpServletRequest req,
|
|
||||||
ModelMap model,
|
|
||||||
HttpSession session)
|
|
||||||
{
|
|
||||||
String result = deviceEndpoint.readUserCode(userCode, model, session);
|
|
||||||
if (result.equals(APPROVE_DEVICE) && !perunOidcConfig.getTheme().equalsIgnoreCase("default")) {
|
|
||||||
return themedApproveDevice(model, p, req);
|
|
||||||
} else if (result.equals(REQUEST_USER_CODE) && !perunOidcConfig.getTheme().equalsIgnoreCase("default")) {
|
|
||||||
ControllerUtils.setPageOptions(model, req, htmlClasses, perunOidcConfig);
|
|
||||||
model.put("page", REQUEST_USER_CODE);
|
|
||||||
return "themedRequestUserCode";
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PreAuthorize("hasRole('ROLE_USER')")
|
|
||||||
@PostMapping(value = "/" + DEVICE + "/approve", params = {USER_CODE, USER_OAUTH_APPROVAL})
|
|
||||||
public String approveDevice(@RequestParam(USER_CODE) String userCode,
|
|
||||||
@RequestParam(USER_OAUTH_APPROVAL) Boolean approve,
|
|
||||||
@ModelAttribute(USER_OAUTH_APPROVAL) AuthorizationRequest authRequest,
|
|
||||||
Principal p,
|
|
||||||
HttpServletRequest req,
|
|
||||||
ModelMap model,
|
|
||||||
Authentication auth,
|
|
||||||
HttpSession session)
|
|
||||||
{
|
|
||||||
String result = deviceEndpoint.approveDevice(userCode, approve, model, auth, session);
|
|
||||||
if (result.equals(DEVICE_APPROVED) && !perunOidcConfig.getTheme().equalsIgnoreCase("default")) {
|
|
||||||
model.remove("scopes");
|
|
||||||
|
|
||||||
DeviceCode dc = (DeviceCode)session.getAttribute("deviceCode");
|
|
||||||
ClientDetailsEntity client = (ClientDetailsEntity) model.get("client");
|
|
||||||
PerunUserInfo user = (PerunUserInfo) userInfoService.getByUsernameAndClientId(
|
|
||||||
p.getName(), client.getClientId());
|
|
||||||
|
|
||||||
ControllerUtils.setScopesAndClaims(scopeService, scopeClaimTranslationService, model, dc.getScope(), user);
|
|
||||||
ControllerUtils.setPageOptions(model, req, htmlClasses, perunOidcConfig);
|
|
||||||
|
|
||||||
model.put("page", DEVICE_APPROVED);
|
|
||||||
return "themedDeviceApproved";
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String themedApproveDevice(ModelMap model, Principal p, HttpServletRequest req) {
|
|
||||||
model.remove("scopes");
|
|
||||||
DeviceCode dc = (DeviceCode) model.get("dc");
|
|
||||||
ClientDetailsEntity client = (ClientDetailsEntity) model.get("client");
|
|
||||||
PerunUserInfo user = (PerunUserInfo) userInfoService.getByUsernameAndClientId(
|
|
||||||
p.getName(), client.getClientId());
|
|
||||||
ControllerUtils.setScopesAndClaims(scopeService, scopeClaimTranslationService, model, dc.getScope(), user);
|
|
||||||
ControllerUtils.setPageOptions(model, req, htmlClasses, perunOidcConfig);
|
|
||||||
|
|
||||||
model.put("page", APPROVE_DEVICE);
|
|
||||||
return "themedApproveDevice";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -7,7 +7,6 @@ import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import cz.muni.ics.oauth2.model.SystemScope;
|
import cz.muni.ics.oauth2.model.SystemScope;
|
||||||
import cz.muni.ics.oauth2.service.SystemScopeService;
|
import cz.muni.ics.oauth2.service.SystemScopeService;
|
||||||
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.web.WebHtmlClasses;
|
import cz.muni.ics.oidc.web.WebHtmlClasses;
|
||||||
import cz.muni.ics.openid.connect.model.UserInfo;
|
import cz.muni.ics.openid.connect.model.UserInfo;
|
||||||
|
@ -25,6 +24,8 @@ import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import cz.muni.ics.openid.connect.service.ScopeClaimTranslationService;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.http.NameValuePair;
|
import org.apache.http.NameValuePair;
|
||||||
import org.apache.http.client.utils.URIBuilder;
|
import org.apache.http.client.utils.URIBuilder;
|
||||||
|
@ -148,7 +149,7 @@ public class ControllerUtils {
|
||||||
* @param user userInfo object
|
* @param user userInfo object
|
||||||
*/
|
*/
|
||||||
public static void setScopesAndClaims(SystemScopeService scopeService,
|
public static void setScopesAndClaims(SystemScopeService scopeService,
|
||||||
PerunScopeClaimTranslationService translationService,
|
ScopeClaimTranslationService translationService,
|
||||||
Map<String, Object> model,
|
Map<String, Object> model,
|
||||||
Set<String> scope,
|
Set<String> scope,
|
||||||
UserInfo user) {
|
UserInfo user) {
|
||||||
|
|
|
@ -1,79 +0,0 @@
|
||||||
package cz.muni.ics.oidc.web.controllers;
|
|
||||||
|
|
||||||
|
|
||||||
import cz.muni.ics.oauth2.model.ClientDetailsEntity;
|
|
||||||
import cz.muni.ics.oauth2.service.SystemScopeService;
|
|
||||||
import cz.muni.ics.oauth2.web.OAuthConfirmationController;
|
|
||||||
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.openid.connect.service.UserInfoService;
|
|
||||||
import java.security.Principal;
|
|
||||||
import java.util.Map;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.security.oauth2.provider.AuthorizationRequest;
|
|
||||||
import org.springframework.stereotype.Controller;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.SessionAttributes;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller of the pages where user accepts that information
|
|
||||||
* about him will be sent to the client.
|
|
||||||
*
|
|
||||||
* @author Dominik František Bučík <bucik@ics.muni.cz>
|
|
||||||
* @author Peter Jancus <jancus@ics.muni.cz>
|
|
||||||
*/
|
|
||||||
@Controller
|
|
||||||
@SessionAttributes("authorizationRequest")
|
|
||||||
public class PerunOAuthConfirmationController{
|
|
||||||
|
|
||||||
public static final String APPROVE = "approve";
|
|
||||||
|
|
||||||
private final OAuthConfirmationController oAuthConfirmationController;
|
|
||||||
private final UserInfoService userInfoService;
|
|
||||||
private final PerunOidcConfig perunOidcConfig;
|
|
||||||
private final SystemScopeService scopeService;
|
|
||||||
private final PerunScopeClaimTranslationService scopeClaimTranslationService;
|
|
||||||
private final WebHtmlClasses htmlClasses;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public PerunOAuthConfirmationController(OAuthConfirmationController oAuthConfirmationController,
|
|
||||||
UserInfoService userInfoService,
|
|
||||||
PerunOidcConfig perunOidcConfig,
|
|
||||||
SystemScopeService scopeService,
|
|
||||||
PerunScopeClaimTranslationService scopeClaimTranslationService,
|
|
||||||
WebHtmlClasses htmlClasses)
|
|
||||||
{
|
|
||||||
this.oAuthConfirmationController = oAuthConfirmationController;
|
|
||||||
this.userInfoService = userInfoService;
|
|
||||||
this.perunOidcConfig = perunOidcConfig;
|
|
||||||
this.scopeService = scopeService;
|
|
||||||
this.scopeClaimTranslationService = scopeClaimTranslationService;
|
|
||||||
this.htmlClasses = htmlClasses;
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequestMapping(value = "/oauth/confirm_access", params = { "client_id" })
|
|
||||||
public String confirmAccess(Map<String, Object> model, HttpServletRequest req, Principal p)
|
|
||||||
{
|
|
||||||
AuthorizationRequest authRequest = (AuthorizationRequest)model.get("authorizationRequest");
|
|
||||||
String result = oAuthConfirmationController.confirmAccess(model, p);
|
|
||||||
if (result.equals(APPROVE) && !perunOidcConfig.getTheme().equalsIgnoreCase("default")) {
|
|
||||||
model.remove("scopes");
|
|
||||||
model.remove("claims");
|
|
||||||
ClientDetailsEntity client = (ClientDetailsEntity) model.get("client");
|
|
||||||
PerunUserInfo user = (PerunUserInfo) userInfoService.getByUsernameAndClientId(
|
|
||||||
p.getName(), client.getClientId());
|
|
||||||
ControllerUtils.setScopesAndClaims(scopeService, scopeClaimTranslationService, model, authRequest.getScope(),
|
|
||||||
user);
|
|
||||||
ControllerUtils.setPageOptions(model, req, htmlClasses, perunOidcConfig);
|
|
||||||
|
|
||||||
model.put("page", "consent");
|
|
||||||
return "themedApprove";
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in New Issue