From 72f0ab631ddcf89f9bd219264493ed006183b8a8 Mon Sep 17 00:00:00 2001 From: Justin Richer Date: Fri, 30 Aug 2013 14:41:51 -0400 Subject: [PATCH] added transient structured value to system scope, added scope matcher function to scope service --- .../org/mitre/oauth2/model/SystemScope.java | 182 +++++++++++------- .../oauth2/service/SystemScopeService.java | 15 +- .../impl/DefaultSystemScopeService.java | 119 ++++++------ .../impl/TestDefaultSystemScopeService.java | 41 ++++ 4 files changed, 223 insertions(+), 134 deletions(-) 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 67133e0b4..f03a771a4 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 @@ -28,6 +28,7 @@ import javax.persistence.Id; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.Table; +import javax.persistence.Transient; /** * @author jricher @@ -49,6 +50,7 @@ public class SystemScope { 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; + private String structuredValue; /** * Make a blank system scope with no value @@ -64,6 +66,7 @@ public class SystemScope { public SystemScope(String value) { this.value = value; } + /** * @return the id */ @@ -161,8 +164,11 @@ public class SystemScope { return structured; } - public void setIsStructured(boolean isStructured) { - this.structured = isStructured; + /** + * @param structured the structured to set + */ + public void setStructured(boolean structured) { + this.structured = structured; } @Basic @@ -179,81 +185,117 @@ public class SystemScope { } + /** + * @return the structuredValue + */ + @Transient // we don't save the value of a system scope separately + public String getStructuredValue() { + return structuredValue; + } + + /** + * @param structuredValue the structuredValue to set + */ + public void setStructuredValue(String structuredValue) { + this.structuredValue = structuredValue; + } + + /* (non-Javadoc) * @see java.lang.Object#hashCode() */ - @Override - public int hashCode() { - final int prime = 31; - 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()); - result = prime * result + ((value == null) ? 0 : value.hashCode()); - return result; - } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (allowDynReg ? 1231 : 1237); + result = prime * result + (defaultScope ? 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()); + result = prime * result + (structured ? 1231 : 1237); + result = prime * result + ((structuredParamDescription == null) ? 0 : structuredParamDescription.hashCode()); + result = prime * result + ((structuredValue == null) ? 0 : structuredValue.hashCode()); + result = prime * result + ((value == null) ? 0 : value.hashCode()); + return result; + } /* (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - SystemScope other = (SystemScope) obj; - if (allowDynReg != other.allowDynReg) { - return false; - } - if (defaultScope != other.defaultScope) { - return false; - } - if (structured != other.structured) { - return false; - } - if (description == null) { - if (other.description != null) { - return false; - } - } else if (!description.equals(other.description)) { - return false; - } - if (icon == null) { - if (other.icon != null) { - return false; - } - } else if (!icon.equals(other.icon)) { - return false; - } - if (id == null) { - if (other.id != null) { - return false; - } - } else if (!id.equals(other.id)) { - return false; - } - if (value == null) { - if (other.value != null) { - return false; - } - } else if (!value.equals(other.value)) { - return false; - } - return true; - } + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof SystemScope)) { + return false; + } + SystemScope other = (SystemScope) obj; + if (allowDynReg != other.allowDynReg) { + return false; + } + if (defaultScope != other.defaultScope) { + return false; + } + if (description == null) { + if (other.description != null) { + return false; + } + } else if (!description.equals(other.description)) { + return false; + } + if (icon == null) { + if (other.icon != null) { + return false; + } + } else if (!icon.equals(other.icon)) { + return false; + } + if (id == null) { + if (other.id != null) { + return false; + } + } else if (!id.equals(other.id)) { + return false; + } + if (structured != other.structured) { + return false; + } + if (structuredParamDescription == null) { + if (other.structuredParamDescription != null) { + return false; + } + } else if (!structuredParamDescription.equals(other.structuredParamDescription)) { + return false; + } + if (structuredValue == null) { + if (other.structuredValue != null) { + return false; + } + } else if (!structuredValue.equals(other.structuredValue)) { + return false; + } + if (value == null) { + if (other.value != null) { + return false; + } + } else if (!value.equals(other.value)) { + return false; + } + return true; + } - @Override - public String toString() { - return "SystemScope [value=" + value + ", description=" + description + ", icon=" + icon + ", allowDynReg=" + allowDynReg + ", defaultScope=" + defaultScope + ", isStructured=" + structured + "]"; - } + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "SystemScope [id=" + id + ", value=" + value + ", description=" + description + ", icon=" + icon + ", allowDynReg=" + allowDynReg + ", defaultScope=" + defaultScope + ", structured=" + structured + ", structuredParamDescription=" + structuredParamDescription + ", structuredValue=" + + structuredValue + "]"; + } } diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/service/SystemScopeService.java b/openid-connect-common/src/main/java/org/mitre/oauth2/service/SystemScopeService.java index 9f0da9fda..be6068d3e 100644 --- a/openid-connect-common/src/main/java/org/mitre/oauth2/service/SystemScopeService.java +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/service/SystemScopeService.java @@ -65,11 +65,14 @@ public interface SystemScopeService { * @return */ public Set toStrings(Set scope); - - public String baseScopeString(String value); - - public Map structuredScopeParameters(Set scope); - - public SystemScope toStructuredScope(String s); + /** + * Test whether the scopes in both sets are compatible, with special + * processing for structured scopes. All scopes in "actual" must exist in + * "expected". If a scope in "expected" is structured and has a value, it + * must be matched exactly by its corresponding scope in "actual". If a + * scope in "expected" is structured but has no value, it may be matched by + * a scope with or without a value in "actual". + */ + public boolean scopesMatch(Set expected, Set actual); } 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 b2a45a99d..316df841b 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,11 +19,9 @@ */ package org.mitre.oauth2.service.impl; -import java.util.HashMap; -import java.util.Map; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; -import java.util.logging.Logger; import org.mitre.oauth2.model.SystemScope; import org.mitre.oauth2.repository.SystemScopeRepository; @@ -35,8 +33,9 @@ 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.base.Strings; import com.google.common.collect.Collections2; -import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; import com.google.common.collect.Sets; /** @@ -67,18 +66,26 @@ public class DefaultSystemScopeService implements SystemScopeService { private Function stringToSystemScope = new Function() { @Override public SystemScope apply(String input) { - input = baseScopeString(input); - if (input == null) { + if (Strings.isNullOrEmpty(input)) { return null; } else { - SystemScope s = getByValue(input); - if (s != null) { - // get the real scope if it's available - return s; - } else { + List parts = parseStructuredScopeValue(input); + String base = parts.get(0); // the first part is the base + // get the real scope if it's available + SystemScope s = getByValue(base); + if (s == null) { // make a fake one otherwise - return new SystemScope(input); + s = new SystemScope(base); + if (parts.size() > 1) { + s.setStructured(true); + } } + + if (s.isStructured() && parts.size() > 1) { + s.setStructuredValue(parts.get(1)); + } + + return s; } } }; @@ -175,53 +182,49 @@ public class DefaultSystemScopeService implements SystemScopeService { } } - private String[] scopeParts(String value){ - return Iterables.toArray( - Splitter.on(":").split(value), - String.class); + // parse a structured scope string into its components + private List parseStructuredScopeValue(String value) { + return Lists.newArrayList(Splitter.on(":").split(value)); } - @Override - public String baseScopeString(String value) { - SystemScope s = toStructuredScope(value); - if (s != null) { - return s.getValue(); - } - return value; - } + /* (non-Javadoc) + * @see org.mitre.oauth2.service.SystemScopeService#scopesMatch(java.util.Set, java.util.Set) + */ + @Override + public boolean scopesMatch(Set expected, Set actual) { + + Set ex = fromStrings(expected); + Set act = fromStrings(actual); + + for (SystemScope actScope : act) { + // first check to see if there's an exact match + if (!ex.contains(actScope)) { + // we didn't find an exact match + if (actScope.isStructured() && !Strings.isNullOrEmpty(actScope.getStructuredValue())) { + // if we didn't get an exact match but the actual scope is structured, we need to check further + + // first, find the "base" scope for this + SystemScope base = getByValue(actScope.getValue()); + if (!ex.contains(base)) { + // if the expected doesn't contain the base scope, fail + return false; + } else { + // we did find an exact match, need to check the rest + } + } else { + // the scope wasn't structured, fail now + return false; + } + } else { + // if we did find an exact match, we need to check the rest + } + } + + // if we got all the way down here, the setup passed + return true; + + } + + - @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/test/java/org/mitre/oauth2/service/impl/TestDefaultSystemScopeService.java b/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultSystemScopeService.java index 98bd7c2fa..bd9e252c1 100644 --- a/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultSystemScopeService.java +++ b/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultSystemScopeService.java @@ -26,7 +26,9 @@ import org.mitre.oauth2.repository.SystemScopeRepository; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; import com.google.common.collect.Sets; @@ -147,4 +149,43 @@ public class TestDefaultSystemScopeService { assertThat(service.toStrings(allScopes), equalTo(allScopeStrings)); } + @Test + public void scopesMatch() { + + Set expected = Sets.newHashSet("foo", "bar", "baz"); + Set actualGood = Sets.newHashSet("foo", "baz", "bar"); + Set actualGood2 = Sets.newHashSet("foo", "bar"); + Set actualBad = Sets.newHashSet("foo", "bob", "bar"); + + // same scopes, different order + assertThat(service.scopesMatch(expected, actualGood), is(true)); + + // subset + assertThat(service.scopesMatch(expected, actualGood2), is(true)); + + // extra scope (fail) + assertThat(service.scopesMatch(expected, actualBad), is(false)); + } + + @Test + public void scopesMatch_structured() { + Set expected = Sets.newHashSet("foo", "bar", "baz"); + Set actualGood = Sets.newHashSet("foo:value", "baz", "bar"); + Set actualBad = Sets.newHashSet("foo:value", "bar:value"); + + // note: we have to use "thenAnswer" here to mimic the repository not serializing the structuredValue field + Mockito.when(repository.getByValue("foo")).thenAnswer(new Answer() { + @Override + public SystemScope answer(InvocationOnMock invocation) throws Throwable { + SystemScope foo = new SystemScope("foo"); + foo.setStructured(true); + return foo; + } + + }); + + assertThat(service.scopesMatch(expected, actualGood), is(true)); + + assertThat(service.scopesMatch(expected, actualBad), is(false)); + } }