split required claim sets out as separate indirection structure, closes #813
parent
13239c1754
commit
cd47d32e2d
|
@ -29,6 +29,7 @@ import javax.persistence.GeneratedValue;
|
||||||
import javax.persistence.GenerationType;
|
import javax.persistence.GenerationType;
|
||||||
import javax.persistence.Id;
|
import javax.persistence.Id;
|
||||||
import javax.persistence.JoinColumn;
|
import javax.persistence.JoinColumn;
|
||||||
|
import javax.persistence.JoinTable;
|
||||||
import javax.persistence.NamedQueries;
|
import javax.persistence.NamedQueries;
|
||||||
import javax.persistence.NamedQuery;
|
import javax.persistence.NamedQuery;
|
||||||
import javax.persistence.OneToMany;
|
import javax.persistence.OneToMany;
|
||||||
|
@ -130,7 +131,11 @@ public class PermissionTicket {
|
||||||
* @return the claimsSupplied
|
* @return the claimsSupplied
|
||||||
*/
|
*/
|
||||||
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
|
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
|
||||||
@JoinColumn(name = "permission_ticket_id")
|
@JoinTable(
|
||||||
|
name = "claim_to_permission_ticket",
|
||||||
|
joinColumns = @JoinColumn(name = "permission_ticket_id"),
|
||||||
|
inverseJoinColumns = @JoinColumn(name = "claim_id")
|
||||||
|
)
|
||||||
public Collection<Claim> getClaimsSupplied() {
|
public Collection<Claim> getClaimsSupplied() {
|
||||||
return claimsSupplied;
|
return claimsSupplied;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
/*******************************************************************************
|
||||||
|
* Copyright 2015 The MITRE Corporation
|
||||||
|
* and the MIT Kerberos and Internet Trust Consortium
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*******************************************************************************/
|
||||||
|
|
||||||
|
package org.mitre.uma.model;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.persistence.CascadeType;
|
||||||
|
import javax.persistence.CollectionTable;
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.ElementCollection;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.FetchType;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.GenerationType;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.JoinColumn;
|
||||||
|
import javax.persistence.JoinTable;
|
||||||
|
import javax.persistence.OneToMany;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A set of claims required to fulfill a given permission.
|
||||||
|
*
|
||||||
|
* @author jricher
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "required_claim_set")
|
||||||
|
public class RequiredClaimSet {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private Collection<Claim> claimsRequired;
|
||||||
|
private Set<String> scopes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the id
|
||||||
|
*/
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
@Column(name = "id")
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param id the id to set
|
||||||
|
*/
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the claimsRequired
|
||||||
|
*/
|
||||||
|
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
|
||||||
|
@JoinTable(
|
||||||
|
name = "claim_to_permission_ticket",
|
||||||
|
joinColumns = @JoinColumn(name = "permission_ticket_id"),
|
||||||
|
inverseJoinColumns = @JoinColumn(name = "claim_id")
|
||||||
|
)
|
||||||
|
public Collection<Claim> getClaimsRequired() {
|
||||||
|
return claimsRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param claimsRequired the claimsRequired to set
|
||||||
|
*/
|
||||||
|
public void setClaimsRequired(Collection<Claim> claimsRequired) {
|
||||||
|
this.claimsRequired = claimsRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the scopes
|
||||||
|
*/
|
||||||
|
@ElementCollection(fetch = FetchType.EAGER)
|
||||||
|
@Column(name = "scope")
|
||||||
|
@CollectionTable(
|
||||||
|
name = "resource_set_scope",
|
||||||
|
joinColumns = @JoinColumn(name = "owner_id")
|
||||||
|
)
|
||||||
|
public Set<String> getScopes() {
|
||||||
|
return scopes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param scopes the scopes to set
|
||||||
|
*/
|
||||||
|
public void setScopes(Set<String> scopes) {
|
||||||
|
this.scopes = scopes;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -59,7 +59,7 @@ public class ResourceSet {
|
||||||
private String owner; // username of the person responsible for the registration (either directly or via OAuth token)
|
private String owner; // username of the person responsible for the registration (either directly or via OAuth token)
|
||||||
private String clientId; // client id of the protected resource that registered this resource set via OAuth token
|
private String clientId; // client id of the protected resource that registered this resource set via OAuth token
|
||||||
|
|
||||||
private Collection<Claim> claimsRequired;
|
private Collection<RequiredClaimSet> requiredClaimSets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the id
|
* @return the id
|
||||||
|
@ -199,15 +199,15 @@ public class ResourceSet {
|
||||||
*/
|
*/
|
||||||
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
|
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
|
||||||
@JoinColumn(name = "resource_set_id")
|
@JoinColumn(name = "resource_set_id")
|
||||||
public Collection<Claim> getClaimsRequired() {
|
public Collection<RequiredClaimSet> getRequiredClaimSets() {
|
||||||
return claimsRequired;
|
return requiredClaimSets;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param claimsRequired the claimsRequired to set
|
* @param claimsRequired the claimsRequired to set
|
||||||
*/
|
*/
|
||||||
public void setClaimsRequired(Collection<Claim> claimsRequired) {
|
public void setRequiredClaimSets(Collection<RequiredClaimSet> claimsRequired) {
|
||||||
this.claimsRequired = claimsRequired;
|
this.requiredClaimSets = claimsRequired;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ package org.mitre.uma.service;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
import org.mitre.uma.model.Claim;
|
import org.mitre.uma.model.Claim;
|
||||||
|
import org.mitre.uma.model.RequiredClaimSet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -40,6 +41,6 @@ public interface ClaimsProcessingService {
|
||||||
* @param claimsSupplied the supplied claims to test
|
* @param claimsSupplied the supplied claims to test
|
||||||
* @return the unmatched claims (if any), an empty set if the claims are satisfied, never null
|
* @return the unmatched claims (if any), an empty set if the claims are satisfied, never null
|
||||||
*/
|
*/
|
||||||
public Collection<Claim> claimsAreSatisfied(Collection<Claim> claimsRequired, Collection<Claim> claimsSupplied);
|
public Collection<Claim> claimsAreSatisfied(Collection<RequiredClaimSet> claimsRequired, Collection<Claim> claimsSupplied);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -313,8 +313,26 @@ CREATE TABLE IF NOT EXISTS claim (
|
||||||
friendly_name VARCHAR(1024),
|
friendly_name VARCHAR(1024),
|
||||||
claim_type VARCHAR(1024),
|
claim_type VARCHAR(1024),
|
||||||
claim_value VARCHAR(1024),
|
claim_value VARCHAR(1024),
|
||||||
resource_set_id BIGINT,
|
);
|
||||||
permission_ticket_id BIGINT
|
|
||||||
|
CREATE TABLE IF NOT EXISTS claim_to_claim_set (
|
||||||
|
required_claim_set_id BIGINT NOT NULL,
|
||||||
|
claim_id BIGINT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS claim_to_permission_ticket (
|
||||||
|
permission_ticket_id BIGINT NOT NULL,
|
||||||
|
claim_id BIGINT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS required_claim_set (
|
||||||
|
id BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY,
|
||||||
|
resource_set_id BIGINT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS required_claim_set_scope (
|
||||||
|
owner_id BIGINT NOT NULL,
|
||||||
|
scope VARCHAR(256) NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS claim_token_format (
|
CREATE TABLE IF NOT EXISTS claim_token_format (
|
||||||
|
|
|
@ -21,6 +21,7 @@ import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
|
||||||
import org.mitre.uma.model.Claim;
|
import org.mitre.uma.model.Claim;
|
||||||
|
import org.mitre.uma.model.RequiredClaimSet;
|
||||||
import org.mitre.uma.service.ClaimsProcessingService;
|
import org.mitre.uma.service.ClaimsProcessingService;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@ -34,8 +35,27 @@ import org.springframework.stereotype.Service;
|
||||||
@Service("matchAllClaimsProcessor")
|
@Service("matchAllClaimsProcessor")
|
||||||
public class MatchAllClaimsProcessor implements ClaimsProcessingService {
|
public class MatchAllClaimsProcessor implements ClaimsProcessingService {
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see org.mitre.uma.service.ClaimsProcessingService#claimsAreSatisfied(java.util.Collection, java.util.Collection)
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Collection<Claim> claimsAreSatisfied(Collection<Claim> claimsRequired, Collection<Claim> claimsSupplied) {
|
public Collection<Claim> claimsAreSatisfied(Collection<RequiredClaimSet> claimsRequired, Collection<Claim> claimsSupplied) {
|
||||||
|
Collection<Claim> allUnmatched = new HashSet<>();
|
||||||
|
for (RequiredClaimSet requiredClaimSet : claimsRequired) {
|
||||||
|
Collection<Claim> unmatched = checkIndividualClaims(requiredClaimSet.getClaimsRequired(), claimsSupplied);
|
||||||
|
if (unmatched.isEmpty()) {
|
||||||
|
// we found something that's satisfied the claims, let's go with it!
|
||||||
|
return unmatched;
|
||||||
|
} else {
|
||||||
|
allUnmatched.addAll(unmatched);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, tell the caller that we'll need some set of these fulfilled somehow
|
||||||
|
return allUnmatched;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Collection<Claim> checkIndividualClaims(Collection<Claim> claimsRequired, Collection<Claim> claimsSupplied) {
|
||||||
|
|
||||||
Collection<Claim> claimsUnmatched = new HashSet<>(claimsRequired);
|
Collection<Claim> claimsUnmatched = new HashSet<>(claimsRequired);
|
||||||
|
|
||||||
|
|
|
@ -131,7 +131,7 @@ public class AuthorizationRequestEndpoint {
|
||||||
|
|
||||||
ResourceSet rs = ticket.getPermission().getResourceSet();
|
ResourceSet rs = ticket.getPermission().getResourceSet();
|
||||||
|
|
||||||
if (rs.getClaimsRequired() == null || rs.getClaimsRequired().isEmpty()) {
|
if (rs.getRequiredClaimSets() == null || rs.getRequiredClaimSets().isEmpty()) {
|
||||||
// the required claims are empty, this resource has no way to be authorized
|
// the required claims are empty, this resource has no way to be authorized
|
||||||
|
|
||||||
m.addAttribute(JsonErrorView.ERROR, "not_authorized");
|
m.addAttribute(JsonErrorView.ERROR, "not_authorized");
|
||||||
|
@ -141,7 +141,7 @@ public class AuthorizationRequestEndpoint {
|
||||||
} else {
|
} else {
|
||||||
// claims weren't empty or missing, we need to check against what we have
|
// claims weren't empty or missing, we need to check against what we have
|
||||||
|
|
||||||
Collection<Claim> claimsUnmatched = claimsProcessingService.claimsAreSatisfied(rs.getClaimsRequired(), ticket.getClaimsSupplied());
|
Collection<Claim> claimsUnmatched = claimsProcessingService.claimsAreSatisfied(rs.getRequiredClaimSets(), ticket.getClaimsSupplied());
|
||||||
|
|
||||||
if (claimsUnmatched.isEmpty()) {
|
if (claimsUnmatched.isEmpty()) {
|
||||||
// if the unmatched claims come back empty, by function contract that means we're happy and can issue a token
|
// if the unmatched claims come back empty, by function contract that means we're happy and can issue a token
|
||||||
|
|
|
@ -26,6 +26,7 @@ import org.mitre.openid.connect.view.JsonEntityView;
|
||||||
import org.mitre.openid.connect.view.JsonErrorView;
|
import org.mitre.openid.connect.view.JsonErrorView;
|
||||||
import org.mitre.openid.connect.web.RootController;
|
import org.mitre.openid.connect.web.RootController;
|
||||||
import org.mitre.uma.model.Claim;
|
import org.mitre.uma.model.Claim;
|
||||||
|
import org.mitre.uma.model.RequiredClaimSet;
|
||||||
import org.mitre.uma.model.ResourceSet;
|
import org.mitre.uma.model.ResourceSet;
|
||||||
import org.mitre.uma.service.ResourceSetService;
|
import org.mitre.uma.service.ResourceSetService;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -56,6 +57,13 @@ import static org.mitre.oauth2.web.AuthenticationUtilities.ensureOAuthScope;
|
||||||
@RequestMapping("/" + ClaimsAPI.URL)
|
@RequestMapping("/" + ClaimsAPI.URL)
|
||||||
@PreAuthorize("hasRole('ROLE_USER')")
|
@PreAuthorize("hasRole('ROLE_USER')")
|
||||||
public class ClaimsAPI {
|
public class ClaimsAPI {
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// FIXME
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
// Logger for this class
|
// Logger for this class
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ClaimsAPI.class);
|
private static final Logger logger = LoggerFactory.getLogger(ClaimsAPI.class);
|
||||||
|
|
||||||
|
@ -90,7 +98,7 @@ public class ClaimsAPI {
|
||||||
return HttpCodeView.VIEWNAME;
|
return HttpCodeView.VIEWNAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
m.addAttribute(JsonEntityView.ENTITY, rs.getClaimsRequired());
|
m.addAttribute(JsonEntityView.ENTITY, rs.getRequiredClaimSets());
|
||||||
|
|
||||||
return JsonEntityView.VIEWNAME;
|
return JsonEntityView.VIEWNAME;
|
||||||
}
|
}
|
||||||
|
@ -114,11 +122,11 @@ public class ClaimsAPI {
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
Set<Claim> claims = (new Gson()).fromJson(jsonString, new TypeToken<Set<Claim>>() {}.getType());
|
Set<Claim> claims = (new Gson()).fromJson(jsonString, new TypeToken<Set<Claim>>() {}.getType());
|
||||||
|
|
||||||
rs.setClaimsRequired(claims);
|
//rs.setClaimsRequired(claims);
|
||||||
|
|
||||||
resourceSetService.update(rs, rs);
|
resourceSetService.update(rs, rs);
|
||||||
|
|
||||||
m.addAttribute(JsonEntityView.ENTITY, rs.getClaimsRequired());
|
m.addAttribute(JsonEntityView.ENTITY, rs.getRequiredClaimSets());
|
||||||
|
|
||||||
return JsonEntityView.VIEWNAME;
|
return JsonEntityView.VIEWNAME;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue