Structured Scopes from BB+
parent
6851224e42
commit
b416888b07
|
@ -47,6 +47,8 @@ public class SystemScope {
|
||||||
private String icon; // class of the icon to display on the auth page
|
private String icon; // class of the icon to display on the auth page
|
||||||
private boolean allowDynReg = false; // can a dynamically registered client ask for this scope?
|
private boolean allowDynReg = false; // can a dynamically registered client ask for this scope?
|
||||||
private boolean defaultScope = false; // is this a default scope for newly-registered clients?
|
private boolean defaultScope = false; // is this a default scope for newly-registered clients?
|
||||||
|
private boolean structured = false; // is this a default scope for newly-registered clients?
|
||||||
|
private String structuredParamDescription;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make a blank system scope with no value
|
* Make a blank system scope with no value
|
||||||
|
@ -142,6 +144,7 @@ public class SystemScope {
|
||||||
public boolean isDefaultScope() {
|
public boolean isDefaultScope() {
|
||||||
return defaultScope;
|
return defaultScope;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param defaultScope the defaultScope to set
|
* @param defaultScope the defaultScope to set
|
||||||
*/
|
*/
|
||||||
|
@ -149,6 +152,33 @@ public class SystemScope {
|
||||||
this.defaultScope = defaultScope;
|
this.defaultScope = defaultScope;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the isStructured status
|
||||||
|
*/
|
||||||
|
@Basic
|
||||||
|
@Column(name = "structured")
|
||||||
|
public boolean isStructured() {
|
||||||
|
return structured;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsStructured(boolean isStructured) {
|
||||||
|
this.structured = isStructured;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Basic
|
||||||
|
@Column(name = "structured_param_description")
|
||||||
|
public String getStructuredParamDescription() {
|
||||||
|
return structuredParamDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param isStructured the isStructured to set
|
||||||
|
*/
|
||||||
|
public void setStructuredParamDescription(String d) {
|
||||||
|
this.structuredParamDescription = d;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see java.lang.Object#hashCode()
|
* @see java.lang.Object#hashCode()
|
||||||
*/
|
*/
|
||||||
|
@ -158,6 +188,7 @@ public class SystemScope {
|
||||||
int result = 1;
|
int result = 1;
|
||||||
result = prime * result + (allowDynReg ? 1231 : 1237);
|
result = prime * result + (allowDynReg ? 1231 : 1237);
|
||||||
result = prime * result + (defaultScope ? 1231 : 1237);
|
result = prime * result + (defaultScope ? 1231 : 1237);
|
||||||
|
result = prime * result + (structured ? 1231 : 1237);
|
||||||
result = prime * result + ((description == null) ? 0 : description.hashCode());
|
result = prime * result + ((description == null) ? 0 : description.hashCode());
|
||||||
result = prime * result + ((icon == null) ? 0 : icon.hashCode());
|
result = prime * result + ((icon == null) ? 0 : icon.hashCode());
|
||||||
result = prime * result + ((id == null) ? 0 : id.hashCode());
|
result = prime * result + ((id == null) ? 0 : id.hashCode());
|
||||||
|
@ -186,6 +217,9 @@ public class SystemScope {
|
||||||
if (defaultScope != other.defaultScope) {
|
if (defaultScope != other.defaultScope) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (structured != other.structured) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (description == null) {
|
if (description == null) {
|
||||||
if (other.description != null) {
|
if (other.description != null) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -219,7 +253,7 @@ public class SystemScope {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "SystemScope [value=" + value + ", description=" + description + ", icon=" + icon + ", allowDynReg=" + allowDynReg + ", defaultScope=" + defaultScope + "]";
|
return "SystemScope [value=" + value + ", description=" + description + ", icon=" + icon + ", allowDynReg=" + allowDynReg + ", defaultScope=" + defaultScope + ", isStructured=" + structured + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,11 @@
|
||||||
*/
|
*/
|
||||||
package org.mitre.oauth2.service.impl;
|
package org.mitre.oauth2.service.impl;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import org.mitre.oauth2.model.SystemScope;
|
import org.mitre.oauth2.model.SystemScope;
|
||||||
import org.mitre.oauth2.repository.SystemScopeRepository;
|
import org.mitre.oauth2.repository.SystemScopeRepository;
|
||||||
|
@ -31,7 +34,9 @@ import org.springframework.stereotype.Service;
|
||||||
import com.google.common.base.Function;
|
import com.google.common.base.Function;
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
|
import com.google.common.base.Splitter;
|
||||||
import com.google.common.collect.Collections2;
|
import com.google.common.collect.Collections2;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -62,6 +67,7 @@ public class DefaultSystemScopeService implements SystemScopeService {
|
||||||
private Function<String, SystemScope> stringToSystemScope = new Function<String, SystemScope>() {
|
private Function<String, SystemScope> stringToSystemScope = new Function<String, SystemScope>() {
|
||||||
@Override
|
@Override
|
||||||
public SystemScope apply(String input) {
|
public SystemScope apply(String input) {
|
||||||
|
input = baseScopeString(input);
|
||||||
if (input == null) {
|
if (input == null) {
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
|
@ -169,6 +175,53 @@ public class DefaultSystemScopeService implements SystemScopeService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String[] scopeParts(String value){
|
||||||
|
return Iterables.toArray(
|
||||||
|
Splitter.on(":").split(value),
|
||||||
|
String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String baseScopeString(String value) {
|
||||||
|
SystemScope s = toStructuredScope(value);
|
||||||
|
if (s != null) {
|
||||||
|
return s.getValue();
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SystemScope toStructuredScope(String value) {
|
||||||
|
String[] scopeParts = scopeParts(value);
|
||||||
|
String baseScope = value;
|
||||||
|
if (scopeParts.length == 2) {
|
||||||
|
baseScope = scopeParts[0];
|
||||||
|
}
|
||||||
|
SystemScope s = repository.getByValue(baseScope);
|
||||||
|
if (s != null && s.isStructured()) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> structuredScopeParameters(Set<String> scopes) {
|
||||||
|
HashMap<String, String> ret = new HashMap<String, String>();
|
||||||
|
|
||||||
|
for (String s : scopes){
|
||||||
|
SystemScope structured = toStructuredScope(s);
|
||||||
|
if (structured != null){
|
||||||
|
String[] scopeParts = scopeParts(s);
|
||||||
|
if (scopeParts.length == 2){
|
||||||
|
ret.put(scopeParts[0], scopeParts[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,9 +22,11 @@ import java.util.Set;
|
||||||
|
|
||||||
import org.mitre.oauth2.model.ClientDetailsEntity;
|
import org.mitre.oauth2.model.ClientDetailsEntity;
|
||||||
import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
|
import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
|
||||||
|
import org.mitre.oauth2.model.SystemScope;
|
||||||
import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
|
import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
|
||||||
import org.mitre.oauth2.service.ClientDetailsEntityService;
|
import org.mitre.oauth2.service.ClientDetailsEntityService;
|
||||||
import org.mitre.oauth2.service.OAuth2TokenEntityService;
|
import org.mitre.oauth2.service.OAuth2TokenEntityService;
|
||||||
|
import org.mitre.oauth2.service.SystemScopeService;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
@ -48,6 +50,9 @@ public class IntrospectionEndpoint {
|
||||||
@Autowired
|
@Autowired
|
||||||
private ClientDetailsEntityService clientService;
|
private ClientDetailsEntityService clientService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SystemScopeService scopeService;
|
||||||
|
|
||||||
private static Logger logger = LoggerFactory.getLogger(IntrospectionEndpoint.class);
|
private static Logger logger = LoggerFactory.getLogger(IntrospectionEndpoint.class);
|
||||||
|
|
||||||
public IntrospectionEndpoint() {
|
public IntrospectionEndpoint() {
|
||||||
|
@ -116,11 +121,24 @@ public class IntrospectionEndpoint {
|
||||||
|
|
||||||
// if it's the same client that the token was issued to, or it at least has all the scopes the token was issued with
|
// if it's the same client that the token was issued to, or it at least has all the scopes the token was issued with
|
||||||
if (authClient.getClientId().equals(tokenClient.getClientId()) || authClient.getScope().containsAll(scopes)) {
|
if (authClient.getClientId().equals(tokenClient.getClientId()) || authClient.getScope().containsAll(scopes)) {
|
||||||
|
|
||||||
// if it's a valid token, we'll print out information on it
|
// if it's a valid token, we'll print out information on it
|
||||||
model.addAttribute("entity", token);
|
model.addAttribute("entity", token);
|
||||||
return "tokenIntrospection";
|
return "tokenIntrospection";
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
boolean scopesConsistent = true;
|
||||||
|
for (String ts : scopes){
|
||||||
|
if (!authClient.getScope().contains(scopeService.baseScopeString(ts))){
|
||||||
|
scopesConsistent = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scopesConsistent) {
|
||||||
|
model.addAttribute("entity", token);
|
||||||
|
return "tokenIntrospection";
|
||||||
|
}
|
||||||
|
|
||||||
logger.error("Verify failed; client tried to introspect a token of an incorrect scope");
|
logger.error("Verify failed; client tried to introspect a token of an incorrect scope");
|
||||||
model.addAttribute("code", HttpStatus.FORBIDDEN);
|
model.addAttribute("code", HttpStatus.FORBIDDEN);
|
||||||
return "httpCodeView";
|
return "httpCodeView";
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
*/
|
*/
|
||||||
package org.mitre.oauth2.web;
|
package org.mitre.oauth2.web;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -123,6 +124,9 @@ public class OAuthConfirmationController {
|
||||||
|
|
||||||
sortedScopes.addAll(Sets.difference(scopes, systemScopes));
|
sortedScopes.addAll(Sets.difference(scopes, systemScopes));
|
||||||
|
|
||||||
|
Map<String, String> proposedParams = scopeService.structuredScopeParameters(clientAuth.getScope());
|
||||||
|
|
||||||
|
model.put("proposedParams", proposedParams);
|
||||||
model.put("scopes", sortedScopes);
|
model.put("scopes", sortedScopes);
|
||||||
|
|
||||||
return "approve";
|
return "approve";
|
||||||
|
|
|
@ -19,6 +19,7 @@ package org.mitre.openid.connect;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
@ -28,11 +29,13 @@ import org.mitre.jwt.signer.service.JwtSigningAndValidationService;
|
||||||
import org.mitre.jwt.signer.service.impl.JWKSetSigningAndValidationServiceCacheService;
|
import org.mitre.jwt.signer.service.impl.JWKSetSigningAndValidationServiceCacheService;
|
||||||
import org.mitre.oauth2.model.ClientDetailsEntity;
|
import org.mitre.oauth2.model.ClientDetailsEntity;
|
||||||
import org.mitre.oauth2.service.ClientDetailsEntityService;
|
import org.mitre.oauth2.service.ClientDetailsEntityService;
|
||||||
|
import org.mitre.oauth2.service.SystemScopeService;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||||
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
|
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
|
||||||
|
import org.springframework.security.oauth2.common.exceptions.InvalidScopeException;
|
||||||
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.ClientDetails;
|
import org.springframework.security.oauth2.provider.ClientDetails;
|
||||||
|
@ -40,7 +43,10 @@ import org.springframework.security.oauth2.provider.DefaultOAuth2RequestFactory;
|
||||||
import org.springframework.security.oauth2.provider.OAuth2Request;
|
import org.springframework.security.oauth2.provider.OAuth2Request;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import com.google.common.base.Joiner;
|
||||||
|
import com.google.common.base.Splitter;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
import com.nimbusds.jose.util.JSONObjectUtils;
|
import com.nimbusds.jose.util.JSONObjectUtils;
|
||||||
import com.nimbusds.jwt.SignedJWT;
|
import com.nimbusds.jwt.SignedJWT;
|
||||||
|
|
||||||
|
@ -54,6 +60,9 @@ public class ConnectOAuth2RequestFactory extends DefaultOAuth2RequestFactory {
|
||||||
@Autowired
|
@Autowired
|
||||||
private JWKSetSigningAndValidationServiceCacheService validators;
|
private JWKSetSigningAndValidationServiceCacheService validators;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SystemScopeService systemScopes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor with arguments
|
* Constructor with arguments
|
||||||
*
|
*
|
||||||
|
|
|
@ -22,6 +22,8 @@ import java.util.Date;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.mitre.oauth2.model.SystemScope;
|
||||||
|
import org.mitre.oauth2.service.SystemScopeService;
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
|
|
||||||
import org.mitre.openid.connect.model.ApprovedSite;
|
import org.mitre.openid.connect.model.ApprovedSite;
|
||||||
|
@ -39,7 +41,9 @@ import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.context.request.RequestContextHolder;
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
|
|
||||||
|
import com.google.common.base.Splitter;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -69,6 +73,8 @@ public class TofuUserApprovalHandler implements UserApprovalHandler {
|
||||||
@Autowired
|
@Autowired
|
||||||
private ClientDetailsService clientDetailsService;
|
private ClientDetailsService clientDetailsService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SystemScopeService systemScopes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the user has already stored a positive approval decision for this site; or if the
|
* Check if the user has already stored a positive approval decision for this site; or if the
|
||||||
|
@ -195,11 +201,21 @@ public class TofuUserApprovalHandler implements UserApprovalHandler {
|
||||||
//registered allowed scopes.
|
//registered allowed scopes.
|
||||||
|
|
||||||
String scope = approvalParams.get(key);
|
String scope = approvalParams.get(key);
|
||||||
|
String baseScope = systemScopes.baseScopeString(scope);
|
||||||
|
SystemScope structured = systemScopes.toStructuredScope(scope);
|
||||||
|
|
||||||
//Make sure this scope is allowed for the given client
|
//Make sure this scope is allowed for the given client
|
||||||
if (client.getScope().contains(scope)) {
|
if (client.getScope().contains(baseScope)) {
|
||||||
allowedScopes.add(scope);
|
// If it's structured, assign the user-specified parameter
|
||||||
|
if (structured != null){
|
||||||
|
String paramValue = approvalParams.get("scopeparam_" + scope);
|
||||||
|
allowedScopes.add(scope + ":"+paramValue);
|
||||||
|
// .. and if it's unstructured, we're all set
|
||||||
|
} else {
|
||||||
|
allowedScopes.add(scope);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -165,6 +165,8 @@ CREATE TABLE IF NOT EXISTS system_scope (
|
||||||
icon VARCHAR(256),
|
icon VARCHAR(256),
|
||||||
allow_dyn_reg BOOLEAN NOT NULL DEFAULT false,
|
allow_dyn_reg BOOLEAN NOT NULL DEFAULT false,
|
||||||
default_scope BOOLEAN NOT NULL DEFAULT false,
|
default_scope BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
structured BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
structured_param_description VARCHAR(256),
|
||||||
UNIQUE (scope)
|
UNIQUE (scope)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,11 @@
|
||||||
${ scope.value }
|
${ scope.value }
|
||||||
</c:otherwise>
|
</c:otherwise>
|
||||||
</c:choose>
|
</c:choose>
|
||||||
|
|
||||||
</label>
|
</label>
|
||||||
|
<c:if test="${ scope.structured }">
|
||||||
|
<input name="scopeparam_${ scope.value }" type="text" value="${proposedParams[scope.value]}" placeholder="${scope.structuredParamDescription}">
|
||||||
|
</c:if>
|
||||||
|
|
||||||
</c:forEach>
|
</c:forEach>
|
||||||
|
|
||||||
|
|
|
@ -142,6 +142,15 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="control-group" id="isStructured">
|
||||||
|
<div class="controls">
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox" <%=isStructured ? 'checked' : '' %>> is a structured scope
|
||||||
|
</label>
|
||||||
|
<p class="help-block">Is the scope structured with structured values like <code>base:extension</code>?</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="well well-small">
|
<div class="well well-small">
|
||||||
<button class="btn btn-small btn-save btn-success"><i class="icon-ok-circle icon-white"></i> Save</button>
|
<button class="btn btn-small btn-save btn-success"><i class="icon-ok-circle icon-white"></i> Save</button>
|
||||||
<button class="btn btn-small btn-cancel"><i class="icon-ban-circle"></i> Cancel</button>
|
<button class="btn btn-small btn-cancel"><i class="icon-ban-circle"></i> Cancel</button>
|
||||||
|
|
Loading…
Reference in New Issue