diff --git a/.circleci/README.md b/.circleci/README.md new file mode 100644 index 0000000000..ccf24b6aa4 --- /dev/null +++ b/.circleci/README.md @@ -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) diff --git a/.circleci/cci_create_release_and_snapshot.sh b/.circleci/cci_create_release_and_snapshot.sh new file mode 100755 index 0000000000..eb41a89992 --- /dev/null +++ b/.circleci/cci_create_release_and_snapshot.sh @@ -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 diff --git a/.circleci/cci_generate_artifact_links.sh b/.circleci/cci_generate_artifact_links.sh new file mode 100755 index 0000000000..6a8d8755cb --- /dev/null +++ b/.circleci/cci_generate_artifact_links.sh @@ -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 "

Last Deployed Artifacts

" >> $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 "" >> $DOWNLOAD_PAGE + else + echo "No uploaded artifacts found." >> $DOWNLOAD_PAGE + fi + + echo "

Last Deployed Sources

" >> $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 "" >> $DOWNLOAD_PAGE + else + echo "No uploaded artifacts found." >> $DOWNLOAD_PAGE + fi + echo "" >> $DOWNLOAD_PAGE +} + +generate_artifact_links $@ \ No newline at end of file diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000..6f909597c1 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,223 @@ +version: 2.1 + +parameters: + release: + type: boolean + default: false + semgrep_scan: + type: boolean + default: false + +orbs: + gresham: gresham-computing/gresham-orb@5.8.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 + + linux-machine: + machine: + image: ubuntu-2204:2023.10.1 + +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 + + semgrep-scan: + executor: linux-machine + resource_class: medium + steps: + - checkout + - gresham/get-whitelister + - gresham/whitelist-add: + pattern: OpenId + kondukto: true + - gresham/semgrep-scan: + kondukto: true + konduktoProject: "openid-connect-server" + konduktoBranch: "${CIRCLE_BRANCH}" + - gresham/whitelist-remove: + pattern: OpenId + kondukto: true +workflows: + build-and-test: + unless: + or: + - << pipeline.parameters.release >> + - << pipeline.parameters.semgrep_scan >> + 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 + + semgrep-scan: + when: << pipeline.parameters.semgrep_scan >> + jobs: + - semgrep-scan: + context: + - gresham-aws + - CircleCi-Gresham-Credentials + + scheduled-security-scan: + triggers: + - schedule: + cron: 0 4 * * 1 + filters: + branches: + only: 1.3.x + jobs: + - semgrep-scan: + name: Semgrep Scan + context: + - gresham-aws + - CircleCi-Gresham-Credentials + +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 diff --git a/.circleci/run_release_workflow.sh b/.circleci/run_release_workflow.sh new file mode 100755 index 0000000000..79b4fdf29c --- /dev/null +++ b/.circleci/run_release_workflow.sh @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 159e487317..5988ac7616 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ 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: - Added changelog - Set default redirect URI resolver strict matching to true diff --git a/README.md b/README.md index 7b9646814f..a109816388 100644 --- a/README.md +++ b/README.md @@ -29,3 +29,9 @@ The authors and key contributors of the project include: Copyright ©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. \ No newline at end of file diff --git a/openid-connect-client/pom.xml b/openid-connect-client/pom.xml index d3bd77eb6b..3cd6074d4d 100644 --- a/openid-connect-client/pom.xml +++ b/openid-connect-client/pom.xml @@ -22,7 +22,7 @@ openid-connect-parent org.mitre - 1.3.3-SNAPSHOT + 1.3.3.GRESHAM-30-SNAPSHOT .. openid-connect-client @@ -45,7 +45,7 @@ ${java-version} - + + <!– BUILD JavaDoc FILES –> org.apache.maven.plugins maven-javadoc-plugin @@ -70,7 +70,7 @@ - + --> diff --git a/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/IntrospectingTokenService.java b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/IntrospectingTokenService.java index 76eaf22257..06ec7f72e5 100644 --- a/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/IntrospectingTokenService.java +++ b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/IntrospectingTokenService.java @@ -21,12 +21,7 @@ import java.io.IOException; import java.net.URI; -import java.util.Calendar; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; +import java.util.*; import org.apache.http.client.HttpClient; import org.apache.http.impl.client.HttpClientBuilder; @@ -41,6 +36,7 @@ import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.security.core.Authentication; 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.util.OAuth2Utils; import org.springframework.security.oauth2.provider.OAuth2Authentication; @@ -237,10 +233,15 @@ private OAuth2Request createStoredRequest(final JsonObject token) { Map parameters = new HashMap<>(); parameters.put("client_id", clientId); 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; } + // Added the protected method to allow custom behaviour + protected Collection parseClientAuthorities(JsonObject token) { + return null; + } + private Authentication createUserAuthentication(JsonObject token) { JsonElement userId = token.get("user_id"); if(userId == null) { diff --git a/openid-connect-common/pom.xml b/openid-connect-common/pom.xml index 6b7772cd40..826675bcfe 100644 --- a/openid-connect-common/pom.xml +++ b/openid-connect-common/pom.xml @@ -22,7 +22,7 @@ openid-connect-parent org.mitre - 1.3.3-SNAPSHOT + 1.3.3.GRESHAM-30-SNAPSHOT .. openid-connect-common @@ -59,6 +59,10 @@ org.springframework.security.oauth spring-security-oauth2 + + net.minidev + json-smart + com.nimbusds nimbus-jose-jwt @@ -85,7 +89,20 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on + + + org.jsoup + jsoup + + + + org.codehaus.groovy + groovy + + + org.spockframework + spock-core @@ -101,7 +118,38 @@ ${java-version} - + + org.codehaus.gmavenplus + gmavenplus-plugin + 1.8.1 + + + + addTestStubSources + compileTests + removeTestStubs + + + + + ${project.build.directory}/generated-groovy-stubs + ${project.build.directory}/generated-groovy-test-stubs + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + + ${project.build.testOutputDirectory} + + **/*Test.java + **/*Spec.java + + + + + <!– BUILD JavaDoc FILES –> org.apache.maven.plugins maven-javadoc-plugin - attach-sources + attach-javadocs jar - + --> diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/model/OAuth2AccessTokenEntity.java b/openid-connect-common/src/main/java/org/mitre/oauth2/model/OAuth2AccessTokenEntity.java index 70924c3696..67f3834a27 100644 --- a/openid-connect-common/src/main/java/org/mitre/oauth2/model/OAuth2AccessTokenEntity.java +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/model/OAuth2AccessTokenEntity.java @@ -242,6 +242,7 @@ public void setRefreshToken(OAuth2RefreshToken refreshToken) { joinColumns=@JoinColumn(name="owner_id"), name="token_scope" ) + @Column(name="scope") public Set getScope() { return scope; } diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/web/UserInfoInterceptor.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/web/UserInfoInterceptor.java index 584b435ff0..f76b8a13e6 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/web/UserInfoInterceptor.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/web/UserInfoInterceptor.java @@ -25,6 +25,9 @@ import javax.servlet.http.HttpServletRequest; 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.UserInfo; import org.mitre.openid.connect.service.UserInfoService; @@ -51,6 +54,8 @@ */ public class UserInfoInterceptor extends HandlerInterceptorAdapter { + private final Safelist safelist = Safelist.none(); + private Gson gson = new GsonBuilder() .registerTypeHierarchyAdapter(GrantedAuthority.class, new JsonSerializer() { @Override @@ -78,9 +83,11 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons if (auth instanceof OIDCAuthenticationToken) { // if they're logging into this server from a remote OIDC server, pass through their user info OIDCAuthenticationToken oidc = (OIDCAuthenticationToken) auth; - if (oidc.getUserInfo() != null) { - request.setAttribute("userInfo", oidc.getUserInfo()); - request.setAttribute("userInfoJson", oidc.getUserInfo().toJson()); + UserInfo userInfo = oidc.getUserInfo(); + if (userInfo != null) { + sanitiseUserInfo(userInfo); + request.setAttribute("userInfo", userInfo); + request.setAttribute("userInfoJson", userInfo.toJson()); } else { request.setAttribute("userInfo", null); request.setAttribute("userInfoJson", "null"); @@ -94,6 +101,7 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons // if we have one, inject it so views can use it if (user != null) { + sanitiseUserInfo(user); request.setAttribute("userInfo", user); request.setAttribute("userInfoJson", user.toJson()); } @@ -104,4 +112,42 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons 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; + } + } diff --git a/openid-connect-common/src/test/groovy/org/mitre/openid/connect/web/UserInfoInterceptorSpec.groovy b/openid-connect-common/src/test/groovy/org/mitre/openid/connect/web/UserInfoInterceptorSpec.groovy new file mode 100644 index 0000000000..dd8933e62a --- /dev/null +++ b/openid-connect-common/src/test/groovy/org/mitre/openid/connect/web/UserInfoInterceptorSpec.groovy @@ -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 | _ + "" | _ + "" | _ + "" | _ + "" | _ + "" | _ + "" | _ + "
" | _ + "" | _ + "