pull/1604/merge
wgoddard-gresham 2023-10-23 04:51:48 -04:00 committed by GitHub
commit a907358c6e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 771 additions and 167 deletions

14
.circleci/README.md Normal file
View File

@ -0,0 +1,14 @@
# How to make changes?
##### Install the CircleCI CLI:
https://circleci.com/docs/2.0/local-cli/#installation
##### Making a change
Change the areas of the .circleci/config.yml file that need to be edited
##### To verify your changes
Any config can be verified, to ensure your changes are valid against the yaml and orb schemas,
from the root of the project, run: `circleci config validate .circleci/config.yml --org-slug gh/gresham-computing --token $CIRCLE_TOKEN`
##### Possible errors:
- Your file must be encoded in UTF-8 (powershell defaulted to UTF-16)
- Must use Unix style line endings (LF, not CRLF)

View File

@ -0,0 +1,40 @@
#!/bin/bash
REPOSITORY=https://github.com/gresham-computing/openid-connect-server
MASTER_BRANCH=1.3.x
function get_version {
local currentVersion=$(mvn -Dexec.executable='echo' -Dexec.args='${project.version}' --non-recursive exec:exec -q)
IFS='-' read -r -a parts <<< "$currentVersion"
local NEXT_NUMBER="$((${parts[1]} + 1))"
RELEASE_VERSION="${parts[0]}"-"${parts[1]}"
NEXT_SNAPSHOT_VERSION="${parts[0]}"-$NEXT_NUMBER-SNAPSHOT
}
function bump_to_release {
mvn -s gresham-nexus-settings/ctc.plugins.settings.xml versions:set -DnewVersion=$RELEASE_VERSION
git tag v$RELEASE_VERSION
echo -e "\nopenid-connect-server release: $RELEASE_VERSION\n"
}
function bump_to_next_snapshot {
mvn -s gresham-nexus-settings/ctc.plugins.settings.xml versions:set -DnewVersion=$NEXT_SNAPSHOT_VERSION
echo -e "\nopenid-connect-server snapshot: $NEXT_SNAPSHOT_VERSION\n"
}
function commit_changes {
git commit -a -m "$1"
}
function push_changes {
git push $REPOSITORY $MASTER_BRANCH --tags
}
get_version
bump_to_release
commit_changes "New openid-connect-server release: ${RELEASE_VERSION}"
push_changes
bump_to_next_snapshot
commit_changes "Next openid-connect-server snapshot: $NEXT_SNAPSHOT_VERSION"
push_changes

View File

@ -0,0 +1,51 @@
#!/bin/bash
HOME=~/project
DOWNLOAD_PAGE=$HOME/download.html
LOG=$HOME/mavenOutput.log
SEARCH_TERMS=(openid-connect uma)
function generate_artifact_links {
EXTENSION=$1
echo "<!DOCTYPE html><html><body><h2>Last Deployed Artifacts</h2>" >> $DOWNLOAD_PAGE
for searchTerm in ${SEARCH_TERMS[@]}; do
jarUrls+=($(grep -Eo '(http|https).*'${searchTerm}'.*[^-sources].'${EXTENSION}' | sort --unique' $LOG))
done
if [[ ! -z $jarUrls ]]; then
echo "<ul>" >> $DOWNLOAD_PAGE
for jarUrl in "${jarUrls[@]}"; do
jarName=$(basename $jarUrl)
echo "<li><a href="$jarUrl">$jarName</a></li>" >> $DOWNLOAD_PAGE
done
echo "</ul>" >> $DOWNLOAD_PAGE
else
echo "No uploaded artifacts found." >> $DOWNLOAD_PAGE
fi
echo "<h2>Last Deployed Sources</h2>" >> $DOWNLOAD_PAGE
# get all sources upload URLs into an array.
for searchTerm in ${SEARCH_TERMS[@]}; do
sourceUrls+=($(grep -Eo '(http|https).*'${searchTerm}'.*[-sources].'${EXTENSION}' | sort --unique' $LOG))
done
#if download links are found
if [[ ! -z $sourceUrls ]]; then
echo "<ul>" >> $DOWNLOAD_PAGE
# write each array entry as a list item URL
for sourceUrl in "${sourceUrls[@]}"
do
sourceName=$(basename $sourceUrl)
echo "<li><a href="$sourceUrl">$sourceName</a></li>" >> $DOWNLOAD_PAGE
done
echo "</ul>" >> $DOWNLOAD_PAGE
else
echo "No uploaded artifacts found." >> $DOWNLOAD_PAGE
fi
echo "</body></html>" >> $DOWNLOAD_PAGE
}
generate_artifact_links $@

175
.circleci/config.yml Normal file
View File

@ -0,0 +1,175 @@
version: 2.1
parameters:
release:
type: boolean
default: false
orbs:
gresham: gresham-computing/gresham-orb@5.1.0
executors:
docker-executor:
docker:
- image: 399104266609.dkr.ecr.eu-west-1.amazonaws.com/circleci-build-images:corretto-8u382
aws_auth:
aws_access_key_id: $GIS_PRD_ECR_INT_BUILD_ACCESS_KEY
aws_secret_access_key: $GIS_PRD_ECR_INT_BUILD_SECRET_ACCESS_KEY
jobs:
build-and-deploy:
executor: docker-executor
steps:
- checkout
- get-maven-settings-file
- restore-cache
- gresham/get-whitelister
- gresham/whitelist-add:
pattern: OpenId
- run:
name: "Setting Maven version"
command: |
MASTER_BRANCH=1.3.x
VERSION=$(mvn -s gresham-nexus-settings/ctc.plugins.settings.xml -Dexec.executable='echo' -Dexec.args='${project.version}' --non-recursive exec:exec -q)
if [[ "${CIRCLE_BRANCH}" != "${MASTER_BRANCH}" && "${VERSION}" == *-SNAPSHOT ]]; then
mvn -s gresham-nexus-settings/ctc.plugins.settings.xml versions:set -DnewVersion=${CIRCLE_BRANCH}.GRESHAM-SNAPSHOT -B
fi
- run:
name: "Running Maven build and deploy"
command: |
mvn -s gresham-nexus-settings/ctc.plugins.settings.xml clean deploy \
-B -V -U -DskipTests -DskipITs \
-DaltSnapshotDeploymentRepository=snapshots::default::https://nexus.greshamtech.com/repository/thirdparty-maven-snapshots/ \
-DaltReleaseDeploymentRepository=releases::default::https://nexus.greshamtech.com/repository/thirdparty-maven-releases/ \
|& tee -a /home/circleci/project/mavenOutput.log
- generate-download-urls:
extension: jar
- save-cache
- gresham/whitelist-remove:
pattern: OpenId
- persist-workspace
test:
executor: docker-executor
steps:
- attach_workspace:
at: .
- restore-cache
- gresham/get-whitelister
- gresham/whitelist-add:
pattern: OpenId
- run:
name: "Running tests"
command: mvn -fae -s gresham-nexus-settings/ctc.plugins.settings.xml test -B -V -U
- save-test-results
- save-cache
- persist-workspace
- gresham/whitelist-remove:
pattern: OpenId
release:
executor: docker-executor
steps:
- checkout
- get-maven-settings-file
- gresham/get-whitelister
- gresham/whitelist-add:
pattern: OpenId
- restore-cache
- run:
name: Creating openid-connect-server release and next snapshot
command: chmod +x .circleci/cci_create_release_and_snapshot.sh && .circleci/cci_create_release_and_snapshot.sh
- save-cache
- gresham/whitelist-remove:
pattern: OpenId
workflows:
build-and-test:
unless: << pipeline.parameters.release >>
jobs:
- build-and-deploy:
context:
- gresham-aws
- CTC
- CircleCi-Gresham-Credentials
- test:
requires:
- build-and-deploy
context:
- gresham-aws
- CTC
- CircleCi-Gresham-Credentials
build-release:
when: << pipeline.parameters.release >>
jobs:
- release:
context:
- gresham-aws
- CTC
- CircleCi-Gresham-Credentials
filters:
branches:
only: 1.3.x
commands:
setup-git-credentials:
steps:
- run:
name: Setting up Git credentials
command: |
git config --global user.name "CircleCI"
git config --global user.email "$GITHUB_GRESHAM_USER"
get-maven-settings-file:
steps:
- setup-git-credentials
- run:
name: Getting Maven settings file
command: |
git config --global url."https://api:${GITHUB_GRESHAM_PW}@github.com/".insteadOf "https://github.com/"
git clone https://github.com/gresham-computing/gresham-nexus-settings
save-cache:
steps:
- save_cache:
paths:
- ~/.m2
key: v1-m2-{{ .Branch }}-{{ .Environment.CIRCLE_JOB }}-{{ checksum "pom.xml" }}
restore-cache:
steps:
- restore_cache:
keys:
- v1-m2-{{ .Branch }}-{{ .Environment.CIRCLE_JOB }}-{{ checksum "pom.xml" }}
- v1-m2-{{ .Branch }}-{{ .Environment.CIRCLE_JOB }}
- v1-m2-
persist-workspace:
steps:
- persist_to_workspace:
root: .
paths:
- .
generate-download-urls:
parameters:
extension:
type: string
steps:
- run:
name: "Generating artifact download URLs"
command: chmod +x .circleci/cci_generate_artifact_links.sh && .circleci/cci_generate_artifact_links.sh << parameters.extension >>
- store_artifacts:
path: download.html
save-test-results:
steps:
- run:
name: Save test results
command: |
mkdir -p ~/test-results/junit/
find . -type f -regex ".*/target/surefire-reports/.*xml" -exec cp {} ~/test-results/junit/ \;
when: always
- store_test_results:
path: ~/test-results

View File

@ -0,0 +1,29 @@
#!/bin/bash
if [[ -z "${CIRCLE_TOKEN}" ]]; then
echo Cannot trigger release workflow. CircleCI user token not found.
exit 1
fi
BRANCH=1.3.x
echo -e "\nTriggering release workflow on branch: ${BRANCH}.\n"
status_code=$(curl --request POST \
--url https://circleci.com/api/v2/project/github/gresham-computing/openid-connect-server/pipeline \
--header 'Circle-Token: '${CIRCLE_TOKEN}'' \
--header 'content-type: application/json' \
--data '{"branch":"'${BRANCH}'","parameters":{"release": true}}' \
-o response.json \
-w "%{http_code}")
if [ "${status_code}" -ge "200" ] && [ "${status_code}" -lt "300" ]; then
echo -e "\nAPI call succeeded [${status_code}]. Response:\n"
cat response.json
rm response.json
else
echo -e "\nAPI call failed [${status_code}]. Response:\n"
cat response.json
rm response.json
exit 1
fi

View File

@ -1,5 +1,21 @@
Unreleased: Unreleased:
- Updated JDK to Corretto 1.8.342
- Upgraded Jackson Components to 2.15.2
*1.3.3-GRESHAM-28:
- Updated JDK to Corretto 1.8.332
- Upgraded Jackson Components to 2.13.3
*1.3.3-GRESHAM:
- Upgraded libraries with known vulnerabilities
- Added a Gresham specific Jenkinsfile
- Added a password encoder to the client entity service
- Fixes a bug by specifying the name of the scope columnn
- Removed functionality that passed the client secret down to the UI
- Updated JDK to Corretto 1.8.252
*1.3.2: *1.3.2:
- Added changelog - Added changelog
- Set default redirect URI resolver strict matching to true - Set default redirect URI resolver strict matching to true

View File

@ -29,3 +29,9 @@ The authors and key contributors of the project include:
Copyright &copy;2017, [MIT Internet Trust Consortium](http://www.trust.mit.edu/). Licensed under the Apache 2.0 license, for details see `LICENSE.txt`. Copyright &copy;2017, [MIT Internet Trust Consortium](http://www.trust.mit.edu/). Licensed under the Apache 2.0 license, for details see `LICENSE.txt`.
## Release Process
Here at Gresham, we use this component for a base for the auth server, our developing branch is 1.3.x and any feature branches should be made off of that branch.
A release build can be invoked by running .circleci/run_release_workflow.sh shell script. It uses CircleCI API to trigger the release workflow and it requires a CIRCLE_TOKEN environment variable with a personal CircleCI API token to be set. Once triggered, the build will bump appropriate versions to release and then proceed to bump them to next snapshot.

View File

@ -22,7 +22,7 @@
<parent> <parent>
<artifactId>openid-connect-parent</artifactId> <artifactId>openid-connect-parent</artifactId>
<groupId>org.mitre</groupId> <groupId>org.mitre</groupId>
<version>1.3.3-SNAPSHOT</version> <version>1.3.3.GRESHAM-29-SNAPSHOT</version>
<relativePath>..</relativePath> <relativePath>..</relativePath>
</parent> </parent>
<artifactId>openid-connect-client</artifactId> <artifactId>openid-connect-client</artifactId>
@ -45,7 +45,7 @@
<target>${java-version}</target> <target>${java-version}</target>
</configuration> </configuration>
</plugin> </plugin>
<!-- BUILD SOURCE FILES --> <!--&lt;!&ndash; BUILD SOURCE FILES &ndash;&gt;
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId> <artifactId>maven-source-plugin</artifactId>
@ -58,7 +58,7 @@
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
<!-- BUILD JavaDoc FILES --> &lt;!&ndash; BUILD JavaDoc FILES &ndash;&gt;
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId> <artifactId>maven-javadoc-plugin</artifactId>
@ -70,7 +70,7 @@
</goals> </goals>
</execution> </execution>
</executions> </executions>
</plugin> </plugin>-->
</plugins> </plugins>
</build> </build>
</project> </project>

View File

@ -21,12 +21,7 @@ import static org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod.SECRET_BASIC
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.util.Calendar; import java.util.*;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.http.client.HttpClient; import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClientBuilder;
@ -41,6 +36,7 @@ import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.util.OAuth2Utils; import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.OAuth2Authentication;
@ -237,10 +233,15 @@ public class IntrospectingTokenService implements ResourceServerTokenServices {
Map<String, String> parameters = new HashMap<>(); Map<String, String> parameters = new HashMap<>();
parameters.put("client_id", clientId); parameters.put("client_id", clientId);
parameters.put("scope", OAuth2Utils.formatParameterList(scopes)); parameters.put("scope", OAuth2Utils.formatParameterList(scopes));
OAuth2Request storedRequest = new OAuth2Request(parameters, clientId, null, true, scopes, null, null, null, null); OAuth2Request storedRequest = new OAuth2Request(parameters, clientId, parseClientAuthorities(token), true, scopes, null, null, null, null);
return storedRequest; return storedRequest;
} }
// Added the protected method to allow custom behaviour
protected Collection<? extends GrantedAuthority> parseClientAuthorities(JsonObject token) {
return null;
}
private Authentication createUserAuthentication(JsonObject token) { private Authentication createUserAuthentication(JsonObject token) {
JsonElement userId = token.get("user_id"); JsonElement userId = token.get("user_id");
if(userId == null) { if(userId == null) {

View File

@ -22,7 +22,7 @@
<parent> <parent>
<artifactId>openid-connect-parent</artifactId> <artifactId>openid-connect-parent</artifactId>
<groupId>org.mitre</groupId> <groupId>org.mitre</groupId>
<version>1.3.3-SNAPSHOT</version> <version>1.3.3.GRESHAM-29-SNAPSHOT</version>
<relativePath>..</relativePath> <relativePath>..</relativePath>
</parent> </parent>
<artifactId>openid-connect-common</artifactId> <artifactId>openid-connect-common</artifactId>
@ -59,6 +59,10 @@
<groupId>org.springframework.security.oauth</groupId> <groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId> <artifactId>spring-security-oauth2</artifactId>
</dependency> </dependency>
<dependency>
<groupId>net.minidev</groupId>
<artifactId>json-smart</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.nimbusds</groupId> <groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId> <artifactId>nimbus-jose-jwt</artifactId>
@ -85,7 +89,20 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.bouncycastle</groupId> <groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId> <artifactId>bcprov-jdk18on</artifactId>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
</dependency> </dependency>
</dependencies> </dependencies>
@ -101,7 +118,38 @@
<target>${java-version}</target> <target>${java-version}</target>
</configuration> </configuration>
</plugin> </plugin>
<!-- BUILD SOURCE FILES --> <plugin>
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<version>1.8.1</version>
<executions>
<execution>
<goals>
<goal>addTestStubSources</goal>
<goal>compileTests</goal>
<goal>removeTestStubs</goal>
</goals>
</execution>
</executions>
<configuration>
<stubsOutputDirectory>${project.build.directory}/generated-groovy-stubs</stubsOutputDirectory>
<testStubsOutputDirectory>${project.build.directory}/generated-groovy-test-stubs</testStubsOutputDirectory>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<excludedGroups combine.self="override"/>
<testClassesDirectory>${project.build.testOutputDirectory}</testClassesDirectory>
<includes>
<include>**/*Test.java</include>
<include>**/*Spec.java</include>
</includes>
</configuration>
</plugin>
<!--&lt;!&ndash; BUILD SOURCE FILES &ndash;&gt;
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId> <artifactId>maven-source-plugin</artifactId>
@ -114,19 +162,19 @@
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
<!-- BUILD JavaDoc FILES --> &lt;!&ndash; BUILD JavaDoc FILES &ndash;&gt;
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId> <artifactId>maven-javadoc-plugin</artifactId>
<executions> <executions>
<execution> <execution>
<id>attach-sources</id> <id>attach-javadocs</id>
<goals> <goals>
<goal>jar</goal> <goal>jar</goal>
</goals> </goals>
</execution> </execution>
</executions> </executions>
</plugin> </plugin>-->
</plugins> </plugins>
</build> </build>
</project> </project>

View File

@ -242,6 +242,7 @@ public class OAuth2AccessTokenEntity implements OAuth2AccessToken {
joinColumns=@JoinColumn(name="owner_id"), joinColumns=@JoinColumn(name="owner_id"),
name="token_scope" name="token_scope"
) )
@Column(name="scope")
public Set<String> getScope() { public Set<String> getScope() {
return scope; return scope;
} }

View File

@ -25,6 +25,9 @@ import java.lang.reflect.Type;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.jsoup.Jsoup;
import org.jsoup.safety.Safelist;
import org.mitre.openid.connect.model.Address;
import org.mitre.openid.connect.model.OIDCAuthenticationToken; import org.mitre.openid.connect.model.OIDCAuthenticationToken;
import org.mitre.openid.connect.model.UserInfo; import org.mitre.openid.connect.model.UserInfo;
import org.mitre.openid.connect.service.UserInfoService; import org.mitre.openid.connect.service.UserInfoService;
@ -51,6 +54,8 @@ import com.google.gson.JsonSerializer;
*/ */
public class UserInfoInterceptor extends HandlerInterceptorAdapter { public class UserInfoInterceptor extends HandlerInterceptorAdapter {
private final Safelist safelist = Safelist.none();
private Gson gson = new GsonBuilder() private Gson gson = new GsonBuilder()
.registerTypeHierarchyAdapter(GrantedAuthority.class, new JsonSerializer<GrantedAuthority>() { .registerTypeHierarchyAdapter(GrantedAuthority.class, new JsonSerializer<GrantedAuthority>() {
@Override @Override
@ -78,9 +83,11 @@ public class UserInfoInterceptor extends HandlerInterceptorAdapter {
if (auth instanceof OIDCAuthenticationToken) { if (auth instanceof OIDCAuthenticationToken) {
// if they're logging into this server from a remote OIDC server, pass through their user info // if they're logging into this server from a remote OIDC server, pass through their user info
OIDCAuthenticationToken oidc = (OIDCAuthenticationToken) auth; OIDCAuthenticationToken oidc = (OIDCAuthenticationToken) auth;
if (oidc.getUserInfo() != null) { UserInfo userInfo = oidc.getUserInfo();
request.setAttribute("userInfo", oidc.getUserInfo()); if (userInfo != null) {
request.setAttribute("userInfoJson", oidc.getUserInfo().toJson()); sanitiseUserInfo(userInfo);
request.setAttribute("userInfo", userInfo);
request.setAttribute("userInfoJson", userInfo.toJson());
} else { } else {
request.setAttribute("userInfo", null); request.setAttribute("userInfo", null);
request.setAttribute("userInfoJson", "null"); request.setAttribute("userInfoJson", "null");
@ -94,6 +101,7 @@ public class UserInfoInterceptor extends HandlerInterceptorAdapter {
// if we have one, inject it so views can use it // if we have one, inject it so views can use it
if (user != null) { if (user != null) {
sanitiseUserInfo(user);
request.setAttribute("userInfo", user); request.setAttribute("userInfo", user);
request.setAttribute("userInfoJson", user.toJson()); request.setAttribute("userInfoJson", user.toJson());
} }
@ -104,4 +112,42 @@ public class UserInfoInterceptor extends HandlerInterceptorAdapter {
return true; return true;
} }
private void sanitiseUserInfo(final UserInfo userInfo) {
userInfo.setSub(sanitise(userInfo.getSub()));
userInfo.setPreferredUsername(sanitise(userInfo.getPreferredUsername()));
userInfo.setName(sanitise(userInfo.getName()));
userInfo.setGivenName(sanitise(userInfo.getGivenName()));
userInfo.setFamilyName(sanitise(userInfo.getFamilyName()));
userInfo.setMiddleName(sanitise(userInfo.getMiddleName()));
userInfo.setNickname(sanitise(userInfo.getNickname()));
userInfo.setProfile(sanitise(userInfo.getProfile()));
userInfo.setPicture(sanitise(userInfo.getPicture()));
userInfo.setWebsite(sanitise(userInfo.getWebsite()));
userInfo.setEmail(sanitise(userInfo.getEmail()));
userInfo.setGender(sanitise(userInfo.getGender()));
userInfo.setLocale(sanitise(userInfo.getLocale()));
userInfo.setPhoneNumber(sanitise(userInfo.getPhoneNumber()));
userInfo.setUpdatedTime(sanitise(userInfo.getUpdatedTime()));
userInfo.setBirthdate(sanitise(userInfo.getBirthdate()));
Address userInfoAddress = userInfo.getAddress();
if (userInfoAddress != null) {
userInfoAddress.setFormatted(sanitise(userInfoAddress.getFormatted()));
userInfoAddress.setStreetAddress(sanitise(userInfoAddress.getStreetAddress()));
userInfoAddress.setLocality(sanitise(userInfoAddress.getLocality()));
userInfoAddress.setRegion(sanitise(userInfoAddress.getRegion()));
userInfoAddress.setPostalCode(sanitise(userInfoAddress.getPostalCode()));
userInfoAddress.setCountry(sanitise(userInfoAddress.getCountry()));
userInfo.setAddress(userInfoAddress);
}
}
private String sanitise(String elementToClean) {
if (elementToClean != null) {
return Jsoup.clean(elementToClean, safelist);
}
return null;
}
} }

View File

@ -0,0 +1,97 @@
package org.mitre.openid.connect.web
import org.mitre.openid.connect.model.DefaultUserInfo
import org.mitre.openid.connect.model.UserInfo
import spock.lang.Specification
import spock.lang.Unroll
class UserInfoInterceptorSpec extends Specification {
private def userInfoInterceptor = new UserInfoInterceptor()
// CVE-2020-5497 -> https://github.com/mitreid-connect/OpenID-Connect-Java-Spring-Server/issues/1521
@Unroll
def 'User Info is sanitised before making it back to the webpage with payload #payload'() {
given: 'A user name with a malicious payload'
UserInfo userInfo = new DefaultUserInfo()
userInfo.setSub('12318767')
userInfo.setName("Test" + payload + " Test")
userInfo.setPreferredUsername('Test')
userInfo.setGivenName("Test" + payload)
userInfo.setFamilyName('Test')
userInfo.setEmail('test@test.com')
userInfo.setEmailVerified(true)
when: 'The user info object is passed through the sanitise method'
userInfoInterceptor.sanitiseUserInfo(userInfo)
then: 'The malicious names have been sanitised'
userInfo.getName() == 'Test Test'
userInfo.getGivenName() == 'Test'
and: 'The non malicious elements have been unaffected'
userInfo.getSub() == '12318767'
userInfo.getPreferredUsername() == 'Test'
userInfo.getFamilyName() == 'Test'
userInfo.getEmail() == 'test@test.com'
where:
payload | _
"</script><script>alert(1)</script>" | _
"<body src=1 href=1 onerror=\"javascript:alert(1)\"></body>" | _
"<html onMouseWheel html onMouseWheel=\"javascript:javascript:alert(1)\"></html onMouseWheel>" | _
"<IMG SRC=`javascript:javascript:alert(1)`>" | _
"<script ~~~>alert(0%0)</script ~~~>" | _
"<IMG SRC=x onload=\"alert(String.fromCharCode(88,83,83))\">" | _
"<div STYLE=\"background-image: url(&#1;javascript:document.vulnerable=true;)\">" | _
"<BODY ONLOAD=javascript:alert(1)>" | _
"<iframe src=\"vbscript:document.vulnerable=true;\">" | _
"<br SIZE=\"&{document.vulnerable=true}\">" | _
"<img src=\"Mario Heiderich says that svg SHOULD not be executed trough image tags\" onerror=\"javascript:document.write('\\u003c\\u0069\\u0066\\u0072\\u0061\\u006d\\u0065\\u0020\\u0073\\u0072\\u0063\\u003d\\u0022\\u0064\\u0061\\u0074\\u0061\\u003a\\u0069\\u006d\\u0061\\u0067\\u0065\\u002f\\u0073\\u0076\\u0067\\u002b\\u0078\\u006d\\u006c\\u003b\\u0062\\u0061\\u0073\\u0065\\u0036\\u0034\\u002c\\u0050\\u0048\\u004e\\u0032\\u005a\\u0079\\u0042\\u0034\\u0062\\u0057\\u0078\\u0075\\u0063\\u007a\\u0030\\u0069\\u0061\\u0048\\u0052\\u0030\\u0063\\u0044\\u006f\\u0076\\u004c\\u0033\\u0064\\u0033\\u0064\\u0079\\u0035\\u0033\\u004d\\u0079\\u0035\\u0076\\u0063\\u006d\\u0063\\u0076\\u004d\\u006a\\u0041\\u0077\\u004d\\u0043\\u0039\\u007a\\u0064\\u006d\\u0063\\u0069\\u0050\\u0069\\u0041\\u0067\\u0043\\u0069\\u0041\\u0067\\u0049\\u0044\\u0078\\u0070\\u0062\\u0057\\u0046\\u006e\\u005a\\u0053\\u0042\\u0076\\u0062\\u006d\\u0078\\u0076\\u0059\\u0057\\u0051\\u0039\\u0049\\u006d\\u0046\\u0073\\u005a\\u0058\\u004a\\u0030\\u004b\\u0044\\u0045\\u0070\\u0049\\u006a\\u0034\\u0038\\u004c\\u0032\\u006c\\u0074\\u0059\\u0057\\u0064\\u006c\\u0050\\u0069\\u0041\\u0067\\u0043\\u0069\\u0041\\u0067\\u0049\\u0044\\u0078\\u007a\\u0064\\u006d\\u0063\\u0067\\u0062\\u0032\\u0035\\u0073\\u0062\\u0032\\u0046\\u006b\\u0050\\u0053\\u004a\\u0068\\u0062\\u0047\\u0056\\u0079\\u0064\\u0043\\u0067\\u0079\\u004b\\u0053\\u0049\\u002b\\u0050\\u0043\\u0039\\u007a\\u0064\\u006d\\u0063\\u002b\\u0049\\u0043\\u0041\\u004b\\u0049\\u0043\\u0041\\u0067\\u0050\\u0048\\u004e\\u006a\\u0063\\u006d\\u006c\\u0077\\u0064\\u0044\\u0035\\u0068\\u0062\\u0047\\u0056\\u0079\\u0064\\u0043\\u0067\\u007a\\u004b\\u0054\\u0077\\u0076\\u0063\\u0032\\u004e\\u0079\\u0061\\u0058\\u0042\\u0030\\u0050\\u0069\\u0041\\u0067\\u0043\\u0069\\u0041\\u0067\\u0049\\u0044\\u0078\\u006b\\u005a\\u0057\\u005a\\u007a\\u0049\\u0047\\u0039\\u0075\\u0062\\u0047\\u0039\\u0068\\u005a\\u0044\\u0030\\u0069\\u0059\\u0057\\u0078\\u006c\\u0063\\u006e\\u0051\\u006f\\u004e\\u0043\\u006b\\u0069\\u0050\\u006a\\u0077\\u0076\\u005a\\u0047\\u0056\\u006d\\u0063\\u007a\\u0034\\u0067\\u0049\\u0041\\u006f\\u0067\\u0049\\u0043\\u0041\\u0038\\u005a\\u0079\\u0042\\u0076\\u0062\\u006d\\u0078\\u0076\\u0059\\u0057\\u0051\\u0039\\u0049\\u006d\\u0046\\u0073\\u005a\\u0058\\u004a\\u0030\\u004b\\u0044\\u0055\\u0070\\u0049\\u006a\\u0034\\u0067\\u0049\\u0041\\u006f\\u0067\\u0049\\u0043\\u0041\\u0067\\u0049\\u0043\\u0041\\u0067\\u0050\\u0047\\u004e\\u0070\\u0063\\u006d\\u004e\\u0073\\u005a\\u0053\\u0042\\u0076\\u0062\\u006d\\u0078\\u0076\\u0059\\u0057\\u0051\\u0039\\u0049\\u006d\\u0046\\u0073\\u005a\\u0058\\u004a\\u0030\\u004b\\u0044\\u0059\\u0070\\u0049\\u0069\\u0041\\u0076\\u0050\\u0069\\u0041\\u0067\\u0043\\u0069\\u0041\\u0067\\u0049\\u0043\\u0041\\u0067\\u0049\\u0043\\u0041\\u0038\\u0064\\u0047\\u0056\\u0034\\u0064\\u0043\\u0042\\u0076\\u0062\\u006d\\u0078\\u0076\\u0059\\u0057\\u0051\\u0039\\u0049\\u006d\\u0046\\u0073\\u005a\\u0058\\u004a\\u0030\\u004b\\u0044\\u0063\\u0070\\u0049\\u006a\\u0034\\u0038\\u004c\\u0033\\u0052\\u006c\\u0065\\u0048\\u0051\\u002b\\u0049\\u0043\\u0041\\u004b\\u0049\\u0043\\u0041\\u0067\\u0050\\u0043\\u0039\\u006e\\u0050\\u0069\\u0041\\u0067\\u0043\\u006a\\u0077\\u0076\\u0063\\u0033\\u005a\\u006e\\u0050\\u0069\\u0041\\u0067\\u0022\\u003e\\u003c\\u002f\\u0069\\u0066\\u0072\\u0061\\u006d\\u0065\\u003e');\"></img>" | _
}
@Unroll
def 'User Info is santised to an extent to not produce an XSS with payload #payload'() {
given: 'A user name with a malicious payload'
UserInfo userInfo = new DefaultUserInfo()
userInfo.setSub('12318767')
userInfo.setName("Test" + payload + " Test")
userInfo.setPreferredUsername('Test')
userInfo.setGivenName("Test" + payload)
userInfo.setFamilyName('Test')
userInfo.setEmail('test@test.com')
userInfo.setEmailVerified(true)
when: 'The user info object is passed through the sanitise method'
userInfoInterceptor.sanitiseUserInfo(userInfo)
then: 'The malicious names have been sanitised'
userInfo.getName() == 'Test' + expectedResponse + ' Test'
userInfo.getGivenName() == 'Test' + expectedResponse
and: 'The non malicious elements have been unaffected'
userInfo.getSub() == '12318767'
userInfo.getPreferredUsername() == 'Test'
userInfo.getFamilyName() == 'Test'
userInfo.getEmail() == 'test@test.com'
where:
payload | expectedResponse
"'\"></title><script>alert(1111)</script>" | "'\"&gt;"
"'>//\\\\,<'>\">\">\"*\"" | "'&gt;//\\\\,&lt;'&gt;\"&gt;\"&gt;\"*\""
"'\"\"><script language=\"JavaScript\"> alert('X \\nS \\nS');</script>" | "'\"\"&gt;"
"!--\" /><script>alert('xss');</script>" | "!--\" /&gt;"
"\">/XaDoS/><script>alert(document.cookie)</script><script src=\"http://www.site.com/XSS.js\"></script>" | "\"&gt;/XaDoS/&gt;"
}
}

View File

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.mitre</groupId> <groupId>org.mitre</groupId>
<artifactId>openid-connect-parent</artifactId> <artifactId>openid-connect-parent</artifactId>
<version>1.3.3-SNAPSHOT</version> <version>1.3.3.GRESHAM-29-SNAPSHOT</version>
</parent> </parent>
<artifactId>openid-connect-server-webapp</artifactId> <artifactId>openid-connect-server-webapp</artifactId>
<packaging>war</packaging> <packaging>war</packaging>

View File

@ -85,7 +85,9 @@
<c:if test="${ not empty client.logoUri }"> <c:if test="${ not empty client.logoUri }">
<ul class="thumbnails"> <ul class="thumbnails">
<li class="span5"> <li class="span5">
<a class="thumbnail" data-toggle="modal" data-target="#logoModal"><img src="api/clients/${ client.id }/logo" /></a> <a class="thumbnail" data-toggle="modal" data-target="#logoModal">
<img src="<c:out value="${ client.logoUri }" />" referrerpolicy="no-referrer" />
</a>
</li> </li>
</ul> </ul>
<!-- Modal --> <!-- Modal -->
@ -104,7 +106,7 @@
</h3> </h3>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<img src="api/clients/${ client.id }/logo" /> <img src="<c:out value="${ client.logoUri }" />" referrerpolicy="no-referrer" />
<c:if test="${ not empty client.clientUri }"> <c:if test="${ not empty client.clientUri }">
<a href="<c:out value="${ client.clientUri }" />"><c:out value="${ client.clientUri }" /></a> <a href="<c:out value="${ client.clientUri }" />"><c:out value="${ client.clientUri }" /></a>
</c:if> </c:if>

View File

@ -85,7 +85,9 @@
<c:if test="${ not empty client.logoUri }"> <c:if test="${ not empty client.logoUri }">
<ul class="thumbnails"> <ul class="thumbnails">
<li class="span5"> <li class="span5">
<a class="thumbnail" data-toggle="modal" data-target="#logoModal"><img src="api/clients/${ client.id }/logo" /></a> <a class="thumbnail" data-toggle="modal" data-target="#logoModal">
<img src="<c:out value="${ client.logoUri }" />" referrerpolicy="no-referrer" />
</a>
</li> </li>
</ul> </ul>
<!-- Modal --> <!-- Modal -->
@ -104,7 +106,7 @@
</h3> </h3>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<img src="api/clients/${ client.id }/logo" /> <img src="<c:out value="${ client.logoUri }" />" referrerpolicy="no-referrer" />
<c:if test="${ not empty client.clientUri }"> <c:if test="${ not empty client.clientUri }">
<a href="<c:out value="${ client.clientUri }" />"><c:out value="${ client.clientUri }" /></a> <a href="<c:out value="${ client.clientUri }" />"><c:out value="${ client.clientUri }" /></a>
</c:if> </c:if>

View File

@ -26,7 +26,9 @@
<div class="media"> <div class="media">
<% if (client.logoUri) { %> <% if (client.logoUri) { %>
<span class="pull-left"><img class="media-object client-logo" src="api/clients/<%- client.id %>/logo"></span> <span class="pull-left">
<img class="media-object client-logo" src="<%- client.logoUri %>" referrerpolicy="no-referrer" />
</span>
<% } %> <% } %>
<div class="media-body"> <div class="media-body">

View File

@ -23,7 +23,7 @@
<parent> <parent>
<groupId>org.mitre</groupId> <groupId>org.mitre</groupId>
<artifactId>openid-connect-parent</artifactId> <artifactId>openid-connect-parent</artifactId>
<version>1.3.3-SNAPSHOT</version> <version>1.3.3.GRESHAM-29-SNAPSHOT</version>
<relativePath>..</relativePath> <relativePath>..</relativePath>
</parent> </parent>
<build> <build>
@ -53,7 +53,7 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.commons</groupId> <groupId>commons-io</groupId>
<artifactId>commons-io</artifactId> <artifactId>commons-io</artifactId>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@ -32,7 +32,7 @@ import com.google.common.base.Strings;
/** /**
* *
* A redirect resolver that knows how to check against the blacklisted URIs * A redirect resolver that knows how to check against the blacklisted URIs
* for forbidden values. Can be configured to do strict string matching also. * for forbidden values.
* *
* @author jricher * @author jricher
* *
@ -46,8 +46,6 @@ public class BlacklistAwareRedirectResolver extends DefaultRedirectResolver {
@Autowired @Autowired
private ConfigurationPropertiesBean config; private ConfigurationPropertiesBean config;
private boolean strictMatch = true;
/* (non-Javadoc) /* (non-Javadoc)
* @see org.springframework.security.oauth2.provider.endpoint.RedirectResolver#resolveRedirect(java.lang.String, org.springframework.security.oauth2.provider.ClientDetails) * @see org.springframework.security.oauth2.provider.endpoint.RedirectResolver#resolveRedirect(java.lang.String, org.springframework.security.oauth2.provider.ClientDetails)
*/ */
@ -62,44 +60,4 @@ public class BlacklistAwareRedirectResolver extends DefaultRedirectResolver {
return redirect; return redirect;
} }
} }
/* (non-Javadoc)
* @see org.springframework.security.oauth2.provider.endpoint.DefaultRedirectResolver#redirectMatches(java.lang.String, java.lang.String)
*/
@Override
protected boolean redirectMatches(String requestedRedirect, String redirectUri) {
if (isStrictMatch()) {
// we're doing a strict string match for all clients
return Strings.nullToEmpty(requestedRedirect).equals(redirectUri);
} else {
// otherwise do the prefix-match from the library
return super.redirectMatches(requestedRedirect, redirectUri);
}
}
/**
* @return the strictMatch
*/
public boolean isStrictMatch() {
if (config.isHeartMode()) {
// HEART mode enforces strict matching
return true;
} else {
return strictMatch;
}
}
/**
* Set this to true to require exact string matches for all redirect URIs. (Default is false)
*
* @param strictMatch the strictMatch to set
*/
public void setStrictMatch(boolean strictMatch) {
this.strictMatch = strictMatch;
}
} }

View File

@ -50,6 +50,7 @@ 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.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.exceptions.InvalidClientException; import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -100,7 +101,11 @@ public class DefaultOAuth2ClientDetailsEntityService implements ClientDetailsEnt
@Autowired @Autowired
private ConfigurationPropertiesBean config; private ConfigurationPropertiesBean config;
// map of sector URI -> list of redirect URIs @Autowired
private PasswordEncoder passwordEncoder;
// map of sector URI -> list of redirect URIs
private LoadingCache<String, List<String>> sectorRedirects = CacheBuilder.newBuilder() private LoadingCache<String, List<String>> sectorRedirects = CacheBuilder.newBuilder()
.expireAfterAccess(1, TimeUnit.HOURS) .expireAfterAccess(1, TimeUnit.HOURS)
.maximumSize(100) .maximumSize(100)
@ -145,8 +150,16 @@ public class DefaultOAuth2ClientDetailsEntityService implements ClientDetailsEnt
ensureNoReservedScopes(client); ensureNoReservedScopes(client);
String plaintextSecret = client.getClientSecret();
if(!Strings.isNullOrEmpty(client.getClientSecret())) {
client.setClientSecret(this.passwordEncoder.encode(client.getClientSecret()));
}
ClientDetailsEntity c = clientRepository.saveClient(client); ClientDetailsEntity c = clientRepository.saveClient(client);
c.setClientSecret(plaintextSecret);
statsService.resetCache(); statsService.resetCache();
return c; return c;
@ -423,6 +436,12 @@ public class DefaultOAuth2ClientDetailsEntityService implements ClientDetailsEnt
// make sure a client doesn't get any special system scopes // make sure a client doesn't get any special system scopes
ensureNoReservedScopes(newClient); ensureNoReservedScopes(newClient);
if (Strings.isNullOrEmpty(newClient.getClientSecret())){
newClient.setClientSecret(oldClient.getClientSecret());
}else{
newClient.setClientSecret(this.passwordEncoder.encode(newClient.getClientSecret()));
}
return clientRepository.updateClient(oldClient.getId(), newClient); return clientRepository.updateClient(oldClient.getId(), newClient);
} }
throw new IllegalArgumentException("Neither old client or new client can be null!"); throw new IllegalArgumentException("Neither old client or new client can be null!");

View File

@ -103,9 +103,9 @@ public class OAuthConfirmationController {
@PreAuthorize("hasRole('ROLE_USER')") @PreAuthorize("hasRole('ROLE_USER')")
@RequestMapping("/oauth/confirm_access") @RequestMapping("/oauth/confirm_access")
public String confimAccess(Map<String, Object> model, @ModelAttribute("authorizationRequest") AuthorizationRequest authRequest, public String confirmAccess(Map<String, Object> model, Principal p) {
Principal p) {
AuthorizationRequest authRequest = (AuthorizationRequest) model.get("authorizationRequest");
// Check the "prompt" parameter to see if we need to do special processing // Check the "prompt" parameter to see if we need to do special processing
String prompt = (String)authRequest.getExtensions().get(PROMPT); String prompt = (String)authRequest.getExtensions().get(PROMPT);

View File

@ -21,9 +21,11 @@
package org.mitre.oauth2.web; package org.mitre.oauth2.web;
import java.util.Set; import java.util.Set;
import java.util.regex.Pattern;
import org.mitre.oauth2.model.SystemScope; import org.mitre.oauth2.model.SystemScope;
import org.mitre.oauth2.service.SystemScopeService; import org.mitre.oauth2.service.SystemScopeService;
import org.mitre.openid.connect.exception.ScopeException;
import org.mitre.openid.connect.view.HttpCodeView; import org.mitre.openid.connect.view.HttpCodeView;
import org.mitre.openid.connect.view.JsonEntityView; import org.mitre.openid.connect.view.JsonEntityView;
import org.mitre.openid.connect.view.JsonErrorView; import org.mitre.openid.connect.view.JsonErrorView;
@ -33,6 +35,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.security.access.method.P;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap; import org.springframework.ui.ModelMap;
@ -54,6 +57,8 @@ public class ScopeAPI {
public static final String URL = RootController.API_URL + "/scopes"; public static final String URL = RootController.API_URL + "/scopes";
private static final String characterMatcher = "[a-zA-Z]+";
private static final Pattern pattern = Pattern.compile(characterMatcher);
@Autowired @Autowired
private SystemScopeService scopeService; private SystemScopeService scopeService;
@ -101,7 +106,14 @@ public class ScopeAPI {
SystemScope existing = scopeService.getById(id); SystemScope existing = scopeService.getById(id);
SystemScope scope = gson.fromJson(json, SystemScope.class); SystemScope scope = gson.fromJson(json, SystemScope.class);
try {
validateScope(scope);
} catch (ScopeException e) {
logger.error("updateScope failed due to ScopeException", e);
m.put(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
m.put(JsonErrorView.ERROR_MESSAGE, "Could not update scope. The server encountered a scope exception. Contact a system administrator for assistance.");
return JsonErrorView.VIEWNAME;
}
if (existing != null && scope != null) { if (existing != null && scope != null) {
if (existing.getId().equals(scope.getId())) { if (existing.getId().equals(scope.getId())) {
@ -138,6 +150,14 @@ public class ScopeAPI {
SystemScope scope = gson.fromJson(json, SystemScope.class); SystemScope scope = gson.fromJson(json, SystemScope.class);
SystemScope alreadyExists = scopeService.getByValue(scope.getValue()); SystemScope alreadyExists = scopeService.getByValue(scope.getValue());
try {
validateScope(scope);
} catch (ScopeException e) {
logger.error("createScope failed due to ScopeException", e);
m.put(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
m.put(JsonErrorView.ERROR_MESSAGE, "Could not create scope. The server encountered a scope exception. Contact a system administrator for assistance.");
return JsonErrorView.VIEWNAME;
}
if (alreadyExists != null) { if (alreadyExists != null) {
//Error, cannot save a scope with the same value as an existing one //Error, cannot save a scope with the same value as an existing one
logger.error("Error: attempting to save a scope with a value that already exists: " + scope.getValue()); logger.error("Error: attempting to save a scope with a value that already exists: " + scope.getValue());
@ -163,6 +183,12 @@ public class ScopeAPI {
} }
} }
private void validateScope(SystemScope scope) throws ScopeException {
if (!pattern.matcher(scope.getValue()).matches()) {
throw new ScopeException(scope.getValue());
}
}
@PreAuthorize("hasRole('ROLE_ADMIN')") @PreAuthorize("hasRole('ROLE_ADMIN')")
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE) @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
public String deleteScope(@PathVariable("id") Long id, ModelMap m) { public String deleteScope(@PathVariable("id") Long id, ModelMap m) {

View File

@ -0,0 +1,25 @@
/**
* <copyright>
* <p>
* Copyright (c) 2010-2023 Gresham Technologies plc. All rights reserved.
*
* </copyright>
*/
package org.mitre.openid.connect.exception;
/**
* @author hwsmith
*/
public class ScopeException extends Exception {
private final String invalidScope;
public ScopeException(String invalidScope) {
this.invalidScope = invalidScope;
}
public String getMessage() {
return "The scope " + invalidScope + " is invalid as it contains non-alphabet characters";
}
}

View File

@ -21,6 +21,8 @@ import java.lang.reflect.Type;
import java.sql.SQLIntegrityConstraintViolationException; import java.sql.SQLIntegrityConstraintViolationException;
import java.text.ParseException; import java.text.ParseException;
import java.util.Collection; import java.util.Collection;
import java.util.Set;
import java.util.regex.Pattern;
import javax.persistence.PersistenceException; import javax.persistence.PersistenceException;
@ -33,9 +35,8 @@ import org.mitre.oauth2.model.ClientDetailsEntity.SubjectType;
import org.mitre.oauth2.model.PKCEAlgorithm; import org.mitre.oauth2.model.PKCEAlgorithm;
import org.mitre.oauth2.service.ClientDetailsEntityService; import org.mitre.oauth2.service.ClientDetailsEntityService;
import org.mitre.oauth2.web.AuthenticationUtilities; import org.mitre.oauth2.web.AuthenticationUtilities;
import org.mitre.openid.connect.exception.ScopeException;
import org.mitre.openid.connect.exception.ValidationException; import org.mitre.openid.connect.exception.ValidationException;
import org.mitre.openid.connect.model.CachedImage;
import org.mitre.openid.connect.service.ClientLogoLoadingService;
import org.mitre.openid.connect.view.ClientEntityViewForAdmins; import org.mitre.openid.connect.view.ClientEntityViewForAdmins;
import org.mitre.openid.connect.view.ClientEntityViewForUsers; import org.mitre.openid.connect.view.ClientEntityViewForUsers;
import org.mitre.openid.connect.view.HttpCodeView; import org.mitre.openid.connect.view.HttpCodeView;
@ -45,10 +46,8 @@ 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.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.util.OAuth2Utils; import org.springframework.security.oauth2.common.util.OAuth2Utils;
@ -130,11 +129,11 @@ public class ClientAPI {
public static final String URL = RootController.API_URL + "/clients"; public static final String URL = RootController.API_URL + "/clients";
@Autowired private static final String characterMatcher = "[a-zA-Z]+";
private ClientDetailsEntityService clientService; private static final Pattern pattern = Pattern.compile(characterMatcher);
@Autowired @Autowired
private ClientLogoLoadingService clientLogoLoadingService; private ClientDetailsEntityService clientService;
@Autowired @Autowired
@Qualifier("clientAssertionValidator") @Qualifier("clientAssertionValidator")
@ -229,6 +228,9 @@ public class ClientAPI {
public String apiGetAllClients(Model model, Authentication auth) { public String apiGetAllClients(Model model, Authentication auth) {
Collection<ClientDetailsEntity> clients = clientService.getAllClients(); Collection<ClientDetailsEntity> clients = clientService.getAllClients();
clients.forEach(client -> client.setClientSecret(null));
model.addAttribute(JsonEntityView.ENTITY, clients); model.addAttribute(JsonEntityView.ENTITY, clients);
if (AuthenticationUtilities.isAdmin(auth)) { if (AuthenticationUtilities.isAdmin(auth)) {
@ -256,6 +258,12 @@ public class ClientAPI {
json = parser.parse(jsonString).getAsJsonObject(); json = parser.parse(jsonString).getAsJsonObject();
client = gson.fromJson(json, ClientDetailsEntity.class); client = gson.fromJson(json, ClientDetailsEntity.class);
client = validateSoftwareStatement(client); client = validateSoftwareStatement(client);
validateScopes(client.getScope());
} catch (ScopeException e) {
logger.error("apiAddClient failed due to ScopeException", e);
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Could not save new client. The server encountered a scope exception. Contact a system administrator for assistance.");
return JsonErrorView.VIEWNAME;
} catch (JsonSyntaxException e) { } catch (JsonSyntaxException e) {
logger.error("apiAddClient failed due to JsonSyntaxException", e); logger.error("apiAddClient failed due to JsonSyntaxException", e);
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
@ -320,6 +328,8 @@ public class ClientAPI {
try { try {
ClientDetailsEntity newClient = clientService.saveNewClient(client); ClientDetailsEntity newClient = clientService.saveNewClient(client);
//Set the client secret to the plaintext from the request
m.addAttribute(JsonEntityView.ENTITY, newClient); m.addAttribute(JsonEntityView.ENTITY, newClient);
if (AuthenticationUtilities.isAdmin(auth)) { if (AuthenticationUtilities.isAdmin(auth)) {
@ -367,6 +377,12 @@ public class ClientAPI {
json = parser.parse(jsonString).getAsJsonObject(); json = parser.parse(jsonString).getAsJsonObject();
client = gson.fromJson(json, ClientDetailsEntity.class); client = gson.fromJson(json, ClientDetailsEntity.class);
client = validateSoftwareStatement(client); client = validateSoftwareStatement(client);
validateScopes(client.getScope());
} catch (ScopeException e) {
logger.error("apiUpdateClient failed due to ScopeException", e);
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Could not update client. The server encountered a scope exception. Contact a system administrator for assistance.");
return JsonErrorView.VIEWNAME;
} catch (JsonSyntaxException e) { } catch (JsonSyntaxException e) {
logger.error("apiUpdateClient failed due to JsonSyntaxException", e); logger.error("apiUpdateClient failed due to JsonSyntaxException", e);
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
@ -385,6 +401,7 @@ public class ClientAPI {
} }
ClientDetailsEntity oldClient = clientService.getClientById(id); ClientDetailsEntity oldClient = clientService.getClientById(id);
String plaintextSecret = client.getClientSecret();
if (oldClient == null) { if (oldClient == null) {
logger.error("apiUpdateClient failed; client with id " + id + " could not be found."); logger.error("apiUpdateClient failed; client with id " + id + " could not be found.");
@ -408,10 +425,10 @@ public class ClientAPI {
|| client.getTokenEndpointAuthMethod().equals(AuthMethod.SECRET_POST) || client.getTokenEndpointAuthMethod().equals(AuthMethod.SECRET_POST)
|| client.getTokenEndpointAuthMethod().equals(AuthMethod.SECRET_JWT)) { || client.getTokenEndpointAuthMethod().equals(AuthMethod.SECRET_JWT)) {
// if they've asked for us to generate a client secret (or they left it blank but require one), do so here // Once a client has been created, we only update the secret when asked to
if (json.has("generateClientSecret") && json.get("generateClientSecret").getAsBoolean() if (json.has("generateClientSecret") && json.get("generateClientSecret").getAsBoolean()) {
|| Strings.isNullOrEmpty(client.getClientSecret())) {
client = clientService.generateClientSecret(client); client = clientService.generateClientSecret(client);
plaintextSecret = client.getClientSecret();
} }
} else if (client.getTokenEndpointAuthMethod().equals(AuthMethod.PRIVATE_KEY)) { } else if (client.getTokenEndpointAuthMethod().equals(AuthMethod.PRIVATE_KEY)) {
@ -438,6 +455,10 @@ public class ClientAPI {
try { try {
ClientDetailsEntity newClient = clientService.updateClient(oldClient, client); ClientDetailsEntity newClient = clientService.updateClient(oldClient, client);
//Set the client secret to the plaintext from the request
newClient.setClientSecret(plaintextSecret);
m.addAttribute(JsonEntityView.ENTITY, newClient); m.addAttribute(JsonEntityView.ENTITY, newClient);
if (AuthenticationUtilities.isAdmin(auth)) { if (AuthenticationUtilities.isAdmin(auth)) {
@ -453,6 +474,14 @@ public class ClientAPI {
} }
} }
private void validateScopes(Set<String> scopes) throws ScopeException {
for (String s : scopes) {
if (!pattern.matcher(s).matches()) {
throw new ScopeException(s);
}
}
}
/** /**
* Delete a client * Delete a client
* @param id * @param id
@ -497,6 +526,9 @@ public class ClientAPI {
return JsonErrorView.VIEWNAME; return JsonErrorView.VIEWNAME;
} }
//We don't want the UI to get the secret
client.setClientSecret(null);
model.addAttribute(JsonEntityView.ENTITY, client); model.addAttribute(JsonEntityView.ENTITY, client);
if (AuthenticationUtilities.isAdmin(auth)) { if (AuthenticationUtilities.isAdmin(auth)) {
@ -506,31 +538,6 @@ public class ClientAPI {
} }
} }
/**
* Get the logo image for a client
* @param id
*/
@RequestMapping(value = "/{id}/logo", method=RequestMethod.GET, produces = { MediaType.IMAGE_GIF_VALUE, MediaType.IMAGE_JPEG_VALUE, MediaType.IMAGE_PNG_VALUE })
public ResponseEntity<byte[]> getClientLogo(@PathVariable("id") Long id, Model model) {
ClientDetailsEntity client = clientService.getClientById(id);
if (client == null) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
} else if (Strings.isNullOrEmpty(client.getLogoUri())) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
} else {
// get the image from cache
CachedImage image = clientLogoLoadingService.getLogo(client);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType(image.getContentType()));
headers.setContentLength(image.getLength());
return new ResponseEntity<>(image.getData(), headers, HttpStatus.OK);
}
}
private ClientDetailsEntity validateSoftwareStatement(ClientDetailsEntity newClient) throws ValidationException { private ClientDetailsEntity validateSoftwareStatement(ClientDetailsEntity newClient) throws ValidationException {
if (newClient.getSoftwareStatement() != null) { if (newClient.getSoftwareStatement() != null) {
if (assertionValidator.isValid(newClient.getSoftwareStatement())) { if (assertionValidator.isValid(newClient.getSoftwareStatement())) {

View File

@ -150,6 +150,7 @@ public class DynamicClientRegistrationEndpoint {
* @param p * @param p
* @return * @return
*/ */
@PreAuthorize("hasRole('ROLE_USER')")
@RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public String registerNewClient(@RequestBody String jsonString, Model m) { public String registerNewClient(@RequestBody String jsonString, Model m) {
@ -242,6 +243,7 @@ public class DynamicClientRegistrationEndpoint {
// send it all out to the view // send it all out to the view
RegisteredClient registered = new RegisteredClient(savedClient, token.getValue(), config.getIssuer() + "register/" + UriUtils.encodePathSegment(savedClient.getClientId(), "UTF-8")); RegisteredClient registered = new RegisteredClient(savedClient, token.getValue(), config.getIssuer() + "register/" + UriUtils.encodePathSegment(savedClient.getClientId(), "UTF-8"));
m.addAttribute("client", registered); m.addAttribute("client", registered);
m.addAttribute(HttpCodeView.CODE, HttpStatus.CREATED); // http 201 m.addAttribute(HttpCodeView.CODE, HttpStatus.CREATED); // http 201
@ -377,6 +379,9 @@ public class DynamicClientRegistrationEndpoint {
RegisteredClient registered = new RegisteredClient(savedClient, token.getValue(), config.getIssuer() + "register/" + UriUtils.encodePathSegment(savedClient.getClientId(), "UTF-8")); RegisteredClient registered = new RegisteredClient(savedClient, token.getValue(), config.getIssuer() + "register/" + UriUtils.encodePathSegment(savedClient.getClientId(), "UTF-8"));
// We don't want the UI to receive the client secret
registered.setClientSecret(null);
// send it all out to the view // send it all out to the view
m.addAttribute("client", registered); m.addAttribute("client", registered);
m.addAttribute(HttpCodeView.CODE, HttpStatus.OK); // http 200 m.addAttribute(HttpCodeView.CODE, HttpStatus.OK); // http 200

View File

@ -91,6 +91,7 @@ public class ProtectedResourceRegistrationEndpoint {
* @param p * @param p
* @return * @return
*/ */
@PreAuthorize("hasRole('ROLE_USER')")
@RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public String registerNewProtectedResource(@RequestBody String jsonString, Model m) { public String registerNewProtectedResource(@RequestBody String jsonString, Model m) {

View File

@ -22,7 +22,10 @@ package org.mitre.openid.connect.web;
import java.security.Principal; import java.security.Principal;
import java.util.Collection; import java.util.Collection;
import java.util.Set;
import java.util.regex.Pattern;
import org.mitre.openid.connect.exception.ScopeException;
import org.mitre.openid.connect.model.WhitelistedSite; import org.mitre.openid.connect.model.WhitelistedSite;
import org.mitre.openid.connect.service.WhitelistedSiteService; import org.mitre.openid.connect.service.WhitelistedSiteService;
import org.mitre.openid.connect.view.HttpCodeView; import org.mitre.openid.connect.view.HttpCodeView;
@ -56,6 +59,8 @@ import com.google.gson.JsonParser;
public class WhitelistAPI { public class WhitelistAPI {
public static final String URL = RootController.API_URL + "/whitelist"; public static final String URL = RootController.API_URL + "/whitelist";
private static final String characterMatcher = "[a-zA-Z]+";
private static final Pattern pattern = Pattern.compile(characterMatcher);
@Autowired @Autowired
private WhitelistedSiteService whitelistService; private WhitelistedSiteService whitelistService;
@ -100,7 +105,12 @@ public class WhitelistAPI {
try { try {
json = parser.parse(jsonString).getAsJsonObject(); json = parser.parse(jsonString).getAsJsonObject();
whitelist = gson.fromJson(json, WhitelistedSite.class); whitelist = gson.fromJson(json, WhitelistedSite.class);
validateWhitelistScopes(whitelist.getAllowedScopes());
} catch (ScopeException e) {
logger.error("addNewWhitelistedSite failed due to ScopeException", e);
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Could not save new whitelisted site. The server encountered a scopes exception. Contact a system administrator for assistance.");
return JsonErrorView.VIEWNAME;
} catch (JsonParseException e) { } catch (JsonParseException e) {
logger.error("addNewWhitelistedSite failed due to JsonParseException", e); logger.error("addNewWhitelistedSite failed due to JsonParseException", e);
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
@ -137,7 +147,12 @@ public class WhitelistAPI {
try { try {
json = parser.parse(jsonString).getAsJsonObject(); json = parser.parse(jsonString).getAsJsonObject();
whitelist = gson.fromJson(json, WhitelistedSite.class); whitelist = gson.fromJson(json, WhitelistedSite.class);
validateWhitelistScopes(whitelist.getAllowedScopes());
} catch (ScopeException e) {
logger.error("updateWhitelistedSite failed due to ScopeException", e);
m.put(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
m.put(JsonErrorView.ERROR_MESSAGE, "Could not update whitelisted site. The server encountered a scope exception. Contact a system administrator for assistance.");
return JsonErrorView.VIEWNAME;
} catch (JsonParseException e) { } catch (JsonParseException e) {
logger.error("updateWhitelistedSite failed due to JsonParseException", e); logger.error("updateWhitelistedSite failed due to JsonParseException", e);
m.put(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); m.put(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
@ -167,6 +182,14 @@ public class WhitelistAPI {
} }
} }
private void validateWhitelistScopes(Set<String> scopes) throws ScopeException {
for (String s : scopes) {
if (!pattern.matcher(s).matches()) {
throw new ScopeException(s);
}
}
}
/** /**
* Delete a whitelisted site * Delete a whitelisted site
* *

View File

@ -27,6 +27,7 @@ import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.security.oauth2.common.exceptions.InvalidRequestException; import org.springframework.security.oauth2.common.exceptions.InvalidRequestException;
import org.springframework.security.oauth2.common.exceptions.RedirectMismatchException;
import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetails;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
@ -86,16 +87,12 @@ public class TestBlacklistAwareRedirectResolver {
String res1 = resolver.resolveRedirect(goodUri, client); String res1 = resolver.resolveRedirect(goodUri, client);
assertThat(res1, is(equalTo(goodUri))); assertThat(res1, is(equalTo(goodUri)));
// set the resolver to non-strict and test the path-based redirect resolution
resolver.setStrictMatch(false);
String res2 = resolver.resolveRedirect(pathUri, client);
assertThat(res2, is(equalTo(pathUri)));
}
@Test(expected=RedirectMismatchException.class)
public void testResovleRedirect_incorrect() {
resolver.resolveRedirect(pathUri, client);
} }
@Test(expected = InvalidRequestException.class) @Test(expected = InvalidRequestException.class)
@ -106,7 +103,7 @@ public class TestBlacklistAwareRedirectResolver {
} }
@Test /*@Test
public void testRedirectMatches_default() { public void testRedirectMatches_default() {
// this is not an exact match // this is not an exact match
@ -153,5 +150,5 @@ public class TestBlacklistAwareRedirectResolver {
assertThat(res2, is(true)); assertThat(res2, is(true));
} }
*/
} }

99
pom.xml
View File

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.mitre</groupId> <groupId>org.mitre</groupId>
<artifactId>openid-connect-parent</artifactId> <artifactId>openid-connect-parent</artifactId>
<version>1.3.3-SNAPSHOT</version> <version>1.3.3.GRESHAM-29-SNAPSHOT</version>
<name>MITREid Connect</name> <name>MITREid Connect</name>
<packaging>pom</packaging> <packaging>pom</packaging>
<parent> <parent>
@ -72,6 +72,7 @@
<properties> <properties>
<java-version>1.8</java-version> <java-version>1.8</java-version>
<org.slf4j-version>1.7.25</org.slf4j-version> <org.slf4j-version>1.7.25</org.slf4j-version>
<generateBackupPoms>false</generateBackupPoms>
</properties> </properties>
<description>A reference implementation of OpenID Connect (http://openid.net/connect/), OAuth 2.0, and UMA built on top of Java, Spring, and Spring Security. The project contains a fully functioning server, client, and utility library.</description> <description>A reference implementation of OpenID Connect (http://openid.net/connect/), OAuth 2.0, and UMA built on top of Java, Spring, and Spring Security. The project contains a fully functioning server, client, and utility library.</description>
<url>https://github.com/mitreid-connect</url> <url>https://github.com/mitreid-connect</url>
@ -93,11 +94,6 @@
<artifactId>maven-javadoc-plugin</artifactId> <artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.4</version> <version>2.10.4</version>
</plugin> </plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.7.9</version>
</plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId> <artifactId>maven-checkstyle-plugin</artifactId>
@ -261,24 +257,6 @@
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId> <artifactId>maven-site-plugin</artifactId>
@ -341,10 +319,6 @@
</excludes> </excludes>
</configuration> </configuration>
</plugin> </plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
</plugin>
</reportPlugins> </reportPlugins>
</configuration> </configuration>
</plugin> </plugin>
@ -365,7 +339,7 @@
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId> <artifactId>spring-framework-bom</artifactId>
<version>4.3.7.RELEASE</version> <version>4.3.29.RELEASE</version>
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
@ -374,26 +348,26 @@
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId> <artifactId>jackson-databind</artifactId>
<version>2.9.0.pr2</version> <version>2.15.2</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId> <artifactId>jackson-annotations</artifactId>
<version>2.9.0.pr2</version> <version>2.15.2</version>
</dependency> </dependency>
<!-- Spring Security --> <!-- Spring Security -->
<dependency> <dependency>
<groupId>org.springframework.security</groupId> <groupId>org.springframework.security</groupId>
<artifactId>spring-security-bom</artifactId> <artifactId>spring-security-bom</artifactId>
<version>4.2.4.RELEASE</version> <version>4.2.19.RELEASE</version>
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.security.oauth</groupId> <groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId> <artifactId>spring-security-oauth2</artifactId>
<version>2.1.0.RELEASE</version> <version>2.1.4.RELEASE</version>
</dependency> </dependency>
<!-- Servlet --> <!-- Servlet -->
@ -429,7 +403,7 @@
<dependency> <dependency>
<groupId>org.postgresql</groupId> <groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId> <artifactId>postgresql</artifactId>
<version>42.0.0.jre7</version> <version>42.3.2</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.oracle</groupId> <groupId>com.oracle</groupId>
@ -505,7 +479,7 @@
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<version>4.12</version> <version>4.13.1</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
@ -520,6 +494,24 @@
<version>1.9.5</version> <version>1.9.5</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId>
<version>2.5.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<version>1.3-groovy-2.5</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.codehaus.groovy</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- MITREid Connect components --> <!-- MITREid Connect components -->
<dependency> <dependency>
<groupId>org.mitre</groupId> <groupId>org.mitre</groupId>
@ -564,17 +556,17 @@
<dependency> <dependency>
<groupId>com.google.guava</groupId> <groupId>com.google.guava</groupId>
<artifactId>guava</artifactId> <artifactId>guava</artifactId>
<version>21.0</version> <version>29.0-jre</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.google.code.gson</groupId> <groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId> <artifactId>gson</artifactId>
<version>2.8.0</version> <version>2.8.9</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.httpcomponents</groupId> <groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId> <artifactId>httpclient</artifactId>
<version>4.5.3</version> <version>4.5.13</version>
<exclusions> <exclusions>
<exclusion> <exclusion>
<groupId>commons-logging</groupId> <groupId>commons-logging</groupId>
@ -582,15 +574,26 @@
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency>
<groupId>net.minidev</groupId>
<artifactId>json-smart</artifactId>
<version>2.4.11</version>
</dependency>
<dependency> <dependency>
<groupId>com.nimbusds</groupId> <groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId> <artifactId>nimbus-jose-jwt</artifactId>
<version>5.4</version> <version>7.9</version>
<exclusions>
<exclusion>
<groupId>net.minidev</groupId>
<artifactId>json-smart</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.bouncycastle</groupId> <groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId> <artifactId>bcprov-jdk18on</artifactId>
<version>[1.52,)</version> <version>1.76</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.eclipse.persistence</groupId> <groupId>org.eclipse.persistence</groupId>
@ -598,15 +601,25 @@
<version>2.5.1</version> <version>2.5.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.commons</groupId> <groupId>commons-io</groupId>
<artifactId>commons-io</artifactId> <artifactId>commons-io</artifactId>
<version>1.3.2</version> <version>2.8.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ro.isdc.wro4j</groupId> <groupId>ro.isdc.wro4j</groupId>
<artifactId>wro4j-extensions</artifactId> <artifactId>wro4j-extensions</artifactId>
<version>1.8.0</version> <version>1.8.0</version>
</dependency> </dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.15.3</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.14</version>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>

View File

@ -19,7 +19,7 @@
<parent> <parent>
<groupId>org.mitre</groupId> <groupId>org.mitre</groupId>
<artifactId>openid-connect-parent</artifactId> <artifactId>openid-connect-parent</artifactId>
<version>1.3.3-SNAPSHOT</version> <version>1.3.3.GRESHAM-29-SNAPSHOT</version>
<relativePath>..</relativePath> <relativePath>..</relativePath>
</parent> </parent>
<artifactId>uma-server-webapp</artifactId> <artifactId>uma-server-webapp</artifactId>

View File

@ -19,7 +19,7 @@
<parent> <parent>
<groupId>org.mitre</groupId> <groupId>org.mitre</groupId>
<artifactId>openid-connect-parent</artifactId> <artifactId>openid-connect-parent</artifactId>
<version>1.3.3-SNAPSHOT</version> <version>1.3.3.GRESHAM-29-SNAPSHOT</version>
<relativePath>..</relativePath> <relativePath>..</relativePath>
</parent> </parent>
<artifactId>uma-server</artifactId> <artifactId>uma-server</artifactId>