enhancing claims gathering endpoint

multiparty
Justin Richer 2016-10-07 23:41:59 -04:00
parent f8372666e6
commit 84695aa830
10 changed files with 185 additions and 43 deletions

View File

@ -115,7 +115,7 @@ public class WebfingerIssuerService implements IssuerService {
throw new AuthenticationServiceException("Issuer was in blacklist: " + lr.issuer);
}
return new IssuerServiceResponse(lr.issuer, lr.loginHint, null);
return new IssuerServiceResponse(lr.issuer, lr.loginHint, request.getParameter("target_link_uri"));
} catch (UncheckedExecutionException | ExecutionException e) {
logger.warn("Issue fetching issuer for user input: " + identifier + ": " + e.getMessage());
return null;

View File

@ -19,7 +19,6 @@ package org.mitre.uma.service;
import org.mitre.uma.model.ClaimProcessingResult;
import org.mitre.uma.model.PermissionTicket;
import org.mitre.uma.model.PersistedClaimsToken;
import org.mitre.uma.model.ResourceSet;
/**
@ -41,6 +40,6 @@ public interface ClaimsProcessingService {
* @param ticket the supplied claims to test
* @return the result of the claims processing action
*/
public ClaimProcessingResult claimsAreSatisfied(ResourceSet rs, PermissionTicket ticket, PersistedClaimsToken pct);
public ClaimProcessingResult claimsAreSatisfied(ResourceSet rs, PermissionTicket ticket);
}

View File

@ -55,4 +55,9 @@ public interface PermissionService {
*/
public PermissionTicket updateTicket(PermissionTicket ticket);
/**
* @param ticket
*/
public void removeTicket(PermissionTicket ticket);
}

View File

@ -0,0 +1,75 @@
<%@page import="org.springframework.http.HttpStatus"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib prefix="o" tagdir="/WEB-INF/tags"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags"%>
<%@page import="org.springframework.security.oauth2.common.exceptions.OAuth2Exception"%>
<spring:message code="claims.title" var="title"/>
<o:header title="${title}" />
<o:topbar pageName="Claims" />
<div class="container-fluid main">
<div class="row-fluid">
<div class="offset1 span10">
<div class="hero-unit">
<h1>The client
<c:choose>
<c:when test="${empty client.clientName}">
<em><c:out value="${client.clientId}" /></em>
</c:when>
<c:otherwise>
<em><c:out value="${client.clientName}" /></em>
</c:otherwise>
</c:choose>
is requesting access to the resource set
<c:choose>
<c:when test="${empty resourceSet.name}">
<em><c:out value="${resourceSet.id}" /></em>
</c:when>
<c:otherwise>
<em><c:out value="${resourceSet.name}" /></em>
</c:otherwise>
</c:choose>.
</h1>
<p>This system requires that you identify yourself before the process can continue.</p>
<div>So far, you have provided the following claims:
<ul>
<c:if test="${empty claims}">
<li><b>NONE</b></li>
</c:if>
<c:forEach items="${ claims }" var="claim">
<li>
<b><c:out value="${claim.name}" /></b>: <i><c:out value="${claim.value}" /></i>
<small>(<c:out value="${claim.issuer}" />)</small>
</li>
</c:forEach>
</ul>
</div>
<p>Enter your email address to log in with OpenID Connect</p>
<div class="well">
<div class="row-fluid">
<div class="span8">
<form action="openid_connect_login" method="get">
<input type="text" class="input-xxlarge" name="identifier" id="identifier" />
<input type="hidden" name="target_link_uri" value="rqp_claims/collect" />
<input type="submit" value="Log In" />
</form>
</div>
</div>
</div>
<form action="rqp_claims" method="POST">
<input type="submit" value="Return to Client" class="btn" />
</form>
</div>
</div>
</div>
</div>
<o:footer />

View File

@ -94,6 +94,14 @@ public class DefaultPermissionService implements PermissionService {
}
/* (non-Javadoc)
* @see org.mitre.uma.service.PermissionService#removeTicket(org.mitre.uma.model.PermissionTicket)
*/
@Override
public void removeTicket(PermissionTicket ticket) {
repository.remove(ticket);
}
}

View File

@ -23,7 +23,6 @@ import java.util.HashSet;
import org.mitre.uma.model.Claim;
import org.mitre.uma.model.ClaimProcessingResult;
import org.mitre.uma.model.PermissionTicket;
import org.mitre.uma.model.PersistedClaimsToken;
import org.mitre.uma.model.Policy;
import org.mitre.uma.model.ResourceSet;
import org.mitre.uma.service.ClaimsProcessingService;
@ -43,15 +42,10 @@ public class MatchAllClaimsOnAnyPolicy implements ClaimsProcessingService {
* @see org.mitre.uma.service.ClaimsProcessingService#claimsAreSatisfied(java.util.Collection, java.util.Collection)
*/
@Override
public ClaimProcessingResult claimsAreSatisfied(ResourceSet rs, PermissionTicket ticket, PersistedClaimsToken pct) {
public ClaimProcessingResult claimsAreSatisfied(ResourceSet rs, PermissionTicket ticket) {
Collection<Claim> allUnmatched = new HashSet<>();
Collection<Claim> claimsSupplied = new HashSet<>(ticket.getClaimsSupplied()); // copy the claims out of the ticket
if (pct != null && pct.getClaimsSupplied() != null) {
// add the claims from the PCT if available
claimsSupplied.addAll(pct.getClaimsSupplied());
}
for (Policy policy : rs.getPolicies()) {
Collection<Claim> unmatched = checkIndividualClaims(policy.getClaimsRequired(), claimsSupplied);
Collection<Claim> unmatched = checkIndividualClaims(policy.getClaimsRequired(), ticket.getClaimsSupplied());
if (unmatched.isEmpty()) {
// we found something that's satisfied the claims, let's go with it!
return new ClaimProcessingResult(policy);

View File

@ -17,6 +17,7 @@
package org.mitre.uma.token;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
@ -153,8 +154,15 @@ public class RequestingPartyTokenGranter extends AbstractTokenGranter {
throw new NotAuthorizedException();
} else {
// claims weren't empty or missing, we need to check against what we have
if (pct != null && pct.getClaimsSupplied() != null) {
// add the claims from the PCT if available
Collection<Claim> claimsSupplied = new HashSet<>(ticket.getClaimsSupplied()); // copy the claims out of the ticket
claimsSupplied.addAll(pct.getClaimsSupplied());
ticket.setClaimsSupplied(claimsSupplied);
}
ClaimProcessingResult result = claimsProcessingService.claimsAreSatisfied(rs, ticket, pct);
ClaimProcessingResult result = claimsProcessingService.claimsAreSatisfied(rs, ticket);
if (result.isSatisfied()) {
@ -244,6 +252,9 @@ public class RequestingPartyTokenGranter extends AbstractTokenGranter {
tokenService.revokeAccessToken(incomingRpt);
}
// remove our ticket because it shouldn't be used again
permissionService.removeTicket(ticket);
// create a PCT
PersistedClaimsToken newPct = new PersistedClaimsToken();
newPct.setClientId(client.getClientId());

View File

@ -19,6 +19,8 @@ package org.mitre.uma.web;
import java.util.Set;
import javax.servlet.http.HttpSession;
import org.mitre.oauth2.model.ClientDetailsEntity;
import org.mitre.oauth2.service.ClientDetailsEntityService;
import org.mitre.openid.connect.model.OIDCAuthenticationToken;
@ -26,6 +28,8 @@ import org.mitre.openid.connect.model.UserInfo;
import org.mitre.openid.connect.view.HttpCodeView;
import org.mitre.uma.model.Claim;
import org.mitre.uma.model.PermissionTicket;
import org.mitre.uma.model.ResourceSet;
import org.mitre.uma.service.ClaimsProcessingService;
import org.mitre.uma.service.PermissionService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -53,7 +57,6 @@ import com.google.gson.JsonPrimitive;
*
*/
@Controller
@PreAuthorize("hasRole('ROLE_EXTERNAL_USER')")
@RequestMapping("/" + ClaimsCollectionEndpoint.URL)
public class ClaimsCollectionEndpoint {
// Logger for this class
@ -61,17 +64,26 @@ public class ClaimsCollectionEndpoint {
public static final String URL = "rqp_claims";
// variables for session storage
private static final String TICKET = URL + "-ticket";
private static final String CLIENT = URL + "-client";
private static final String REDIRECT_URI = URL + "-redirect_uri";
private static final String STATE = URL + "-state";
@Autowired
private ClientDetailsEntityService clientService;
@Autowired
private PermissionService permissionService;
@Autowired
private ClaimsProcessingService claimsProcessingService;
@RequestMapping(method = RequestMethod.GET)
public String collectClaims(@RequestParam("client_id") String clientId, @RequestParam(value = "redirect_uri", required = false) String redirectUri,
public String startClaimsCollection(@RequestParam("client_id") String clientId, @RequestParam(value = "redirect_uri", required = false) String redirectUri,
@RequestParam("ticket") String ticketValue, @RequestParam(value = "state", required = false) String state,
Model m, OIDCAuthenticationToken auth) {
Model m, OIDCAuthenticationToken auth, HttpSession session) {
ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
@ -79,42 +91,80 @@ public class ClaimsCollectionEndpoint {
PermissionTicket ticket = permissionService.getByTicket(ticketValue);
if (client == null || ticket == null) {
// couldn't find the client or the ticket, we bail here
logger.info("Client or ticket not found: " + clientId + " :: " + ticketValue);
m.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND);
return HttpCodeView.VIEWNAME;
}
// stash the client and ticket on the session and send the user to the claims endpoint
session.setAttribute(TICKET, ticket);
session.setAttribute(CLIENT, client);
session.setAttribute(REDIRECT_URI, redirectUri);
return "redirect:" + URL + "/collect";
}
@RequestMapping(value = "/collect", method = RequestMethod.GET)
public String collectClaims(Model m, OIDCAuthenticationToken auth, HttpSession session) {
ClientDetailsEntity client = (ClientDetailsEntity) session.getAttribute(CLIENT);
PermissionTicket ticket = (PermissionTicket) session.getAttribute(TICKET);
// we've got a client and ticket, let's attach the claims that we have from the token and userinfo
// subject
Set<Claim> claimsSupplied = Sets.newHashSet(ticket.getClaimsSupplied());
String issuer = auth.getIssuer();
UserInfo userInfo = auth.getUserInfo();
claimsSupplied.add(mkClaim(issuer, "sub", new JsonPrimitive(auth.getSub())));
if (userInfo.getEmail() != null) {
claimsSupplied.add(mkClaim(issuer, "email", new JsonPrimitive(userInfo.getEmail())));
}
if (userInfo.getEmailVerified() != null) {
claimsSupplied.add(mkClaim(issuer, "email_verified", new JsonPrimitive(userInfo.getEmailVerified())));
}
if (userInfo.getPhoneNumber() != null) {
claimsSupplied.add(mkClaim(issuer, "phone_number", new JsonPrimitive(auth.getUserInfo().getPhoneNumber())));
}
if (userInfo.getPhoneNumberVerified() != null) {
claimsSupplied.add(mkClaim(issuer, "phone_number_verified", new JsonPrimitive(auth.getUserInfo().getPhoneNumberVerified())));
}
if (userInfo.getPreferredUsername() != null) {
claimsSupplied.add(mkClaim(issuer, "preferred_username", new JsonPrimitive(auth.getUserInfo().getPreferredUsername())));
}
if (userInfo.getProfile() != null) {
claimsSupplied.add(mkClaim(issuer, "profile", new JsonPrimitive(auth.getUserInfo().getProfile())));
}
ticket.setClaimsSupplied(claimsSupplied);
if (auth != null) {
PermissionTicket updatedTicket = permissionService.updateTicket(ticket);
String issuer = auth.getIssuer();
UserInfo userInfo = auth.getUserInfo();
claimsSupplied.add(mkClaim(issuer, "sub", new JsonPrimitive(auth.getSub())));
if (userInfo.getEmail() != null) {
claimsSupplied.add(mkClaim(issuer, "email", new JsonPrimitive(userInfo.getEmail())));
}
if (userInfo.getEmailVerified() != null) {
claimsSupplied.add(mkClaim(issuer, "email_verified", new JsonPrimitive(userInfo.getEmailVerified())));
}
if (userInfo.getPhoneNumber() != null) {
claimsSupplied.add(mkClaim(issuer, "phone_number", new JsonPrimitive(auth.getUserInfo().getPhoneNumber())));
}
if (userInfo.getPhoneNumberVerified() != null) {
claimsSupplied.add(mkClaim(issuer, "phone_number_verified", new JsonPrimitive(auth.getUserInfo().getPhoneNumberVerified())));
}
if (userInfo.getPreferredUsername() != null) {
claimsSupplied.add(mkClaim(issuer, "preferred_username", new JsonPrimitive(auth.getUserInfo().getPreferredUsername())));
}
if (userInfo.getProfile() != null) {
claimsSupplied.add(mkClaim(issuer, "profile", new JsonPrimitive(auth.getUserInfo().getProfile())));
}
ticket.setClaimsSupplied(claimsSupplied);
ticket = permissionService.updateTicket(ticket);
session.setAttribute(TICKET, ticket);
}
ResourceSet resourceSet = ticket.getPermission().getResourceSet();
m.addAttribute("claims", ticket.getClaimsSupplied());
m.addAttribute("resourceSet", resourceSet);
m.addAttribute("client", client);
m.addAttribute("claimsResult", claimsProcessingService.claimsAreSatisfied(resourceSet, ticket));
return "claims_collection";
}
@RequestMapping(method = RequestMethod.POST)
public String returnToClient(Model m, OIDCAuthenticationToken auth, HttpSession session) {
ClientDetailsEntity client = (ClientDetailsEntity) session.getAttribute(CLIENT);
PermissionTicket ticket = (PermissionTicket) session.getAttribute(TICKET);
String redirectUri = (String) session.getAttribute(REDIRECT_URI);
String state = (String) session.getAttribute(STATE);
if (Strings.isNullOrEmpty(redirectUri)) {
if (client.getClaimsRedirectUris().size() == 1) {
@ -131,7 +181,7 @@ public class ClaimsCollectionEndpoint {
UriComponentsBuilder template = UriComponentsBuilder.fromUriString(redirectUri);
template.queryParam("authorization_state", "claims_submitted");
template.queryParam("ticket", updatedTicket.getTicket());
template.queryParam("ticket", ticket.getTicket());
if (!Strings.isNullOrEmpty(state)) {
template.queryParam("state", state);
}

View File

@ -43,7 +43,7 @@ public class UmaDiscoveryEndpoint {
@Autowired
private ConfigurationPropertiesBean config;
@RequestMapping(".well-known/multiparty-delegation-configuration")
@RequestMapping(".well-known/uma2-configuration")
public String umaConfiguration(Model model) {
Map<String, Object> m = new HashMap<>();