diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/model/SystemScope.java b/openid-connect-common/src/main/java/org/mitre/oauth2/model/SystemScope.java index 851f36ea8..67133e0b4 100644 --- a/openid-connect-common/src/main/java/org/mitre/oauth2/model/SystemScope.java +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/model/SystemScope.java @@ -47,7 +47,9 @@ public class SystemScope { 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 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 */ @@ -142,13 +144,41 @@ public class SystemScope { public boolean isDefaultScope() { return defaultScope; } + /** * @param defaultScope the defaultScope to set */ public void setDefaultScope(boolean 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) * @see java.lang.Object#hashCode() */ @@ -158,6 +188,7 @@ public class SystemScope { int result = 1; result = prime * result + (allowDynReg ? 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 + ((icon == null) ? 0 : icon.hashCode()); result = prime * result + ((id == null) ? 0 : id.hashCode()); @@ -186,6 +217,9 @@ public class SystemScope { if (defaultScope != other.defaultScope) { return false; } + if (structured != other.structured) { + return false; + } if (description == null) { if (other.description != null) { return false; @@ -219,7 +253,7 @@ public class SystemScope { @Override 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 + "]"; } } diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultSystemScopeService.java b/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultSystemScopeService.java index d15e61956..b2a45a99d 100644 --- a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultSystemScopeService.java +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultSystemScopeService.java @@ -19,8 +19,11 @@ */ package org.mitre.oauth2.service.impl; +import java.util.HashMap; +import java.util.Map; import java.util.LinkedHashSet; import java.util.Set; +import java.util.logging.Logger; import org.mitre.oauth2.model.SystemScope; 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.Predicate; import com.google.common.base.Predicates; +import com.google.common.base.Splitter; import com.google.common.collect.Collections2; +import com.google.common.collect.Iterables; import com.google.common.collect.Sets; /** @@ -62,6 +67,7 @@ public class DefaultSystemScopeService implements SystemScopeService { private Function stringToSystemScope = new Function() { @Override public SystemScope apply(String input) { + input = baseScopeString(input); if (input == null) { return null; } 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 structuredScopeParameters(Set scopes) { + HashMap ret = new HashMap(); + + 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; + } + } diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/web/IntrospectionEndpoint.java b/openid-connect-server/src/main/java/org/mitre/oauth2/web/IntrospectionEndpoint.java index 5174f5848..f829d4192 100644 --- a/openid-connect-server/src/main/java/org/mitre/oauth2/web/IntrospectionEndpoint.java +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/web/IntrospectionEndpoint.java @@ -22,9 +22,11 @@ import java.util.Set; import org.mitre.oauth2.model.ClientDetailsEntity; import org.mitre.oauth2.model.OAuth2AccessTokenEntity; +import org.mitre.oauth2.model.SystemScope; import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; import org.mitre.oauth2.service.ClientDetailsEntityService; import org.mitre.oauth2.service.OAuth2TokenEntityService; +import org.mitre.oauth2.service.SystemScopeService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -48,6 +50,9 @@ public class IntrospectionEndpoint { @Autowired private ClientDetailsEntityService clientService; + @Autowired + private SystemScopeService scopeService; + private static Logger logger = LoggerFactory.getLogger(IntrospectionEndpoint.class); 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 (authClient.getClientId().equals(tokenClient.getClientId()) || authClient.getScope().containsAll(scopes)) { - // if it's a valid token, we'll print out information on it model.addAttribute("entity", token); return "tokenIntrospection"; } 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"); model.addAttribute("code", HttpStatus.FORBIDDEN); return "httpCodeView"; diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/web/OAuthConfirmationController.java b/openid-connect-server/src/main/java/org/mitre/oauth2/web/OAuthConfirmationController.java index 9596f01ec..6bc22dd46 100644 --- a/openid-connect-server/src/main/java/org/mitre/oauth2/web/OAuthConfirmationController.java +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/web/OAuthConfirmationController.java @@ -19,6 +19,7 @@ */ package org.mitre.oauth2.web; +import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; @@ -110,7 +111,7 @@ public class OAuthConfirmationController { model.put("redirect_uri", redirect_uri); Set scopes = scopeService.fromStrings(clientAuth.getScope()); - + Set sortedScopes = new LinkedHashSet(scopes.size()); Set systemScopes = scopeService.getAll(); @@ -122,7 +123,10 @@ public class OAuthConfirmationController { } sortedScopes.addAll(Sets.difference(scopes, systemScopes)); - + + Map proposedParams = scopeService.structuredScopeParameters(clientAuth.getScope()); + + model.put("proposedParams", proposedParams); model.put("scopes", sortedScopes); return "approve"; diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/ConnectOAuth2RequestFactory.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/ConnectOAuth2RequestFactory.java index 6dedea6e2..2903c8372 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/ConnectOAuth2RequestFactory.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/ConnectOAuth2RequestFactory.java @@ -19,6 +19,7 @@ package org.mitre.openid.connect; import java.text.ParseException; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; 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.oauth2.model.ClientDetailsEntity; import org.mitre.oauth2.service.ClientDetailsEntityService; +import org.mitre.oauth2.service.SystemScopeService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationServiceException; 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.provider.AuthorizationRequest; 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.stereotype.Component; +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; import com.google.common.base.Strings; +import com.google.common.collect.Iterables; import com.nimbusds.jose.util.JSONObjectUtils; import com.nimbusds.jwt.SignedJWT; @@ -54,6 +60,9 @@ public class ConnectOAuth2RequestFactory extends DefaultOAuth2RequestFactory { @Autowired private JWKSetSigningAndValidationServiceCacheService validators; + @Autowired + private SystemScopeService systemScopes; + /** * Constructor with arguments * diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/token/TofuUserApprovalHandler.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/token/TofuUserApprovalHandler.java index b7ee9d0d8..e9108bda3 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/token/TofuUserApprovalHandler.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/token/TofuUserApprovalHandler.java @@ -22,6 +22,8 @@ import java.util.Date; import java.util.Map; import java.util.Set; +import org.mitre.oauth2.model.SystemScope; +import org.mitre.oauth2.service.SystemScopeService; import javax.servlet.http.HttpSession; 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.ServletRequestAttributes; +import com.google.common.base.Splitter; import com.google.common.base.Strings; +import com.google.common.collect.Iterables; import com.google.common.collect.Sets; /** @@ -69,6 +73,8 @@ public class TofuUserApprovalHandler implements UserApprovalHandler { @Autowired private ClientDetailsService clientDetailsService; + @Autowired + private SystemScopeService systemScopes; /** * 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. 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 - if (client.getScope().contains(scope)) { - allowedScopes.add(scope); + if (client.getScope().contains(baseScope)) { + // 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); + } } + } } diff --git a/openid-connect-server/src/main/resources/db/tables/hsql_database_tables.sql b/openid-connect-server/src/main/resources/db/tables/hsql_database_tables.sql index 2d58c9b48..7e9d87cfa 100644 --- a/openid-connect-server/src/main/resources/db/tables/hsql_database_tables.sql +++ b/openid-connect-server/src/main/resources/db/tables/hsql_database_tables.sql @@ -165,6 +165,8 @@ CREATE TABLE IF NOT EXISTS system_scope ( icon VARCHAR(256), allow_dyn_reg 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) ); diff --git a/openid-connect-server/src/main/webapp/WEB-INF/views/approve.jsp b/openid-connect-server/src/main/webapp/WEB-INF/views/approve.jsp index 5d4777c7e..add1b98f4 100644 --- a/openid-connect-server/src/main/webapp/WEB-INF/views/approve.jsp +++ b/openid-connect-server/src/main/webapp/WEB-INF/views/approve.jsp @@ -92,7 +92,11 @@ ${ scope.value } + + + + diff --git a/openid-connect-server/src/main/webapp/resources/template/scope.html b/openid-connect-server/src/main/webapp/resources/template/scope.html index 6f589c7e2..2fcbdb42f 100644 --- a/openid-connect-server/src/main/webapp/resources/template/scope.html +++ b/openid-connect-server/src/main/webapp/resources/template/scope.html @@ -142,6 +142,15 @@ +
+
+ +

Is the scope structured with structured values like base:extension?

+
+
+