Skip to content

Commit bef2db8

Browse files
Merge branch 'develop' into iglezruiz/cargo-migration
2 parents 309ba12 + cedec71 commit bef2db8

File tree

10 files changed

+186
-8
lines changed

10 files changed

+186
-8
lines changed

run-integration-tests.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ set -eu -o pipefail
33

44
#######################################
55
# main function to run the integration tests
6+
# Global env vars:
7+
# UAA_DOCKER_ARGS: Additional args to pass to docker run
8+
# UAA_GRADLE_INT_TEST_COMMAND: Gradle command to run integration tests (default: integrationTest)
9+
# this could include :cloudfoundry-identity-server:integrationTest --tests to run specific tests
610
#######################################
711
main() {
812
local script_dir; script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
@@ -30,6 +34,8 @@ main() {
3034
--env DB="${DB}" \
3135
--env RUN_TESTS="${RUN_TESTS:-true}" \
3236
--publish 8081:8080 \
37+
${UAA_DOCKER_ARGS:-} \
38+
--env UAA_GRADLE_INT_TEST_COMMAND="${UAA_GRADLE_INT_TEST_COMMAND:-integrationTest}" \
3339
"${DOCKER_IMAGE}" \
3440
/root/uaa/scripts/integration_tests.sh "${PROFILE_NAME}" "${run_as_boot}"
3541
}

run-unit-tests.sh

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@ set -eu -o pipefail
33

44
#######################################
55
# main function to run the unit tests
6+
# Global env vars:
7+
# UAA_DOCKER_ARGS: Additional args to pass to docker run
8+
# UAA_GRADLE_UNIT_TEST_COMMAND: Gradle command to run unit tests (default: test)
9+
# this could include :cloudfoundry-identity-server:test --tests to run specific tests
610
#######################################
711
main() {
8-
local script_dir; script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
12+
local script_dir; script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
913
source "${script_dir}/scripts/lib_timer.sh"
1014
start_timer
1115

@@ -27,6 +31,8 @@ main() {
2731
--volume "${script_dir}":"${container_script_dir}" \
2832
--volume "${gradle_lock_dir}" \
2933
--env DB="${DB}" \
34+
${UAA_DOCKER_ARGS:-} \
35+
--env UAA_GRADLE_UNIT_TEST_COMMAND="${UAA_GRADLE_UNIT_TEST_COMMAND:-test}" \
3036
"${DOCKER_IMAGE}" \
3137
/root/uaa/scripts/unit_tests.sh "${PROFILE_NAME}"
3238
}

scripts/integration_tests.sh

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ set -eu
33

44
#######################################
55
# The main function to run the integration tests within a container
6+
# Global env vars:
7+
# UAA_GRADLE_INT_TEST_COMMAND: Gradle command to run integration tests (default: integrationTest)
8+
# this could include :cloudfoundry-identity-server:integrationTest --tests to run specific tests
69
#######################################
710
function main() {
811
local script_dir; script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
@@ -46,7 +49,7 @@ function main() {
4649
readonly integration_test_code="./gradlew '-Dspring.profiles.active=${test_profile}' \
4750
'-Djava.security.egd=file:/dev/./urandom' \
4851
'-DskipUaaAutoStart=true' \
49-
integrationTest \
52+
${UAA_GRADLE_INT_TEST_COMMAND:-integrationTest} \
5053
--stacktrace \
5154
--console=plain"
5255

scripts/start_docker_database.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ set -eu -o pipefail
55
# main function to start a docker container with the specified database
66
# usage: start_docker_database.sh [db_name]
77
# db_name: one of "mysql(-*)", "postgres(-*)", etc. (default: "mysql")
8+
# Global env vars:
9+
# DEBUG_MODE: if set to any value, enables remote debugging on port 5005
10+
# WEB_MODE: if set to any value, enables web access on port 8080
11+
# UAA_DOCKER_ARGS: Additional args to pass to docker run
812
#######################################
913
main() {
1014
local uaa_dir; uaa_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd )"
@@ -43,6 +47,7 @@ main() {
4347
--publish "${db_port}:${db_port}" \
4448
${DEBUG_MODE:+--publish "5005:5005"} \
4549
${WEB_MODE:+--publish "8080:8080"} \
50+
${UAA_DOCKER_ARGS:-} \
4651
"${DOCKER_IMAGE}"
4752
}
4853

scripts/unit_tests.sh

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ set -eu
33

44
#######################################
55
# The main function to run the unit tests within a container
6+
# Global env vars:
7+
# UAA_GRADLE_UNIT_TEST_COMMAND: Gradle command to run unit tests (default: test)
8+
# this could include :cloudfoundry-identity-server:test --tests to run specific tests
69
#######################################
710
function main() {
811
local script_dir; script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
@@ -23,7 +26,7 @@ function main() {
2326
export GRADLE_OPTS="-Xmx2048m"
2427
./gradlew "-Dspring.profiles.active=${test_profile}" \
2528
"-Djava.security.egd=file:/dev/./urandom" \
26-
test \
29+
${UAA_GRADLE_UNIT_TEST_COMMAND:-test} \
2730
--stacktrace \
2831
--console=plain
2932
popd

server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManager.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@
116116
import static java.util.Collections.singletonMap;
117117
import static java.util.Objects.isNull;
118118
import static java.util.stream.Collectors.toSet;
119+
import static org.cloudfoundry.identity.uaa.constants.OriginKeys.OAUTH20;
119120
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.SUB;
120121
import static org.cloudfoundry.identity.uaa.oauth.token.CompositeToken.ID_TOKEN;
121122
import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_AUTHORIZATION_CODE;
@@ -609,9 +610,14 @@ protected <T extends AbstractExternalOAuthIdentityProviderDefinition<T>> Map<Str
609610
ExternalOAuthCodeToken codeToken,
610611
final IdentityProvider<T> identityProvider
611612
) {
612-
String idToken = getTokenFromCode(codeToken, identityProvider);
613-
codeToken.setIdToken(idToken);
614-
return getClaimsFromToken(idToken, identityProvider);
613+
String tokenFieldName = getTokenFieldName(identityProvider.getConfig());
614+
String token = getTokenFromCode(codeToken, identityProvider);
615+
if ("access_token".equals(tokenFieldName) && token != null && OAUTH20.equals(identityProvider.getType())) {
616+
codeToken.setAccessToken(token);
617+
} else {
618+
codeToken.setIdToken(token);
619+
}
620+
return getClaimsFromToken(token, identityProvider);
615621
}
616622

617623
protected <T extends AbstractExternalOAuthIdentityProviderDefinition<T>> Map<String, Object> getClaimsFromToken(

server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthProviderConfigurator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ public String getIdpAuthenticationUrl(
108108
}
109109

110110
if (OIDCIdentityProviderDefinition.class.equals(definition.getParameterizedClass())) {
111-
var nonceGenerator = new RandomValueStringGenerator(12);
111+
var nonceGenerator = new RandomValueStringGenerator(22);
112112
uriBuilder.queryParam("nonce", nonceGenerator.generate());
113113

114114
Map<String, String> additionalParameters = ofNullable(((OIDCIdentityProviderDefinition) definition).getAdditionalAuthzParameters()).orElse(emptyMap());
@@ -123,7 +123,7 @@ protected static boolean isPkceNeeded(AbstractExternalOAuthIdentityProviderDefin
123123
}
124124

125125
private String generateStateParam() {
126-
return uaaRandomStringUtil.getSecureRandom(10);
126+
return uaaRandomStringUtil.getSecureRandom(22);
127127
}
128128

129129
private String generateCodeVerifier() {

server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerIT.java

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.apache.commons.lang3.RandomStringUtils;
88
import org.cloudfoundry.identity.uaa.authentication.AccountNotPreCreatedException;
99
import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication;
10+
import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal;
1011
import org.cloudfoundry.identity.uaa.authentication.event.IdentityProviderAuthenticationSuccessEvent;
1112
import org.cloudfoundry.identity.uaa.authentication.manager.ExternalGroupAuthorizationEvent;
1213
import org.cloudfoundry.identity.uaa.authentication.manager.InvitedUserAuthenticatedEvent;
@@ -95,6 +96,7 @@
9596
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
9697
import static org.assertj.core.api.Assertions.assertThatThrownBy;
9798
import static org.assertj.core.api.Assertions.fail;
99+
import static org.cloudfoundry.identity.uaa.constants.OriginKeys.OAUTH20;
98100
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.ISS;
99101
import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.GROUP_ATTRIBUTE_NAME;
100102
import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.USER_NAME_ATTRIBUTE_NAME;
@@ -109,6 +111,7 @@
109111
import static org.mockito.Mockito.eq;
110112
import static org.mockito.Mockito.mock;
111113
import static org.mockito.Mockito.never;
114+
import static org.mockito.Mockito.reset;
112115
import static org.mockito.Mockito.same;
113116
import static org.mockito.Mockito.spy;
114117
import static org.mockito.Mockito.times;
@@ -515,6 +518,55 @@ void discoveryURL_is_used() throws MalformedURLException {
515518

516519
}
517520

521+
@Test
522+
void oauth20_flow_works_with_non_jwt_token() throws Exception {
523+
String userInfoResponse = """
524+
{
525+
"login": "octocat",
526+
"id": 1,
527+
"type": "User",
528+
"site_admin": false,
529+
"name": "monalisa octocat",
530+
"company": "GitHub",
531+
"email": "[email protected]"
532+
}""";
533+
534+
CompositeToken accessToken = getCompositeAccessToken();
535+
accessToken.setIdTokenValue(null); //DOES NOT EXIST FOR OAUTH2.0
536+
String oauth2TokenResponse = JsonUtils.writeValueAsString(accessToken);
537+
538+
//UAA exchanges the code for a token
539+
mockUaaServer.expect(requestTo("http://localhost/oauth/token"))
540+
.andExpect(header("Authorization", "Basic " + new String(Base64.encodeBase64("identity:identitysecret".getBytes()))))
541+
.andExpect(header("Accept", "application/json"))
542+
.andExpect(content().string(containsString("grant_type=authorization_code")))
543+
.andExpect(content().string(containsString("code=the_code")))
544+
.andExpect(content().string(containsString("redirect_uri=http%3A%2F%2Flocalhost%2Fcallback%2Fthe_origin")))
545+
.andExpect(content().string(containsString("response_type=code")))
546+
.andRespond(withStatus(OK).contentType(APPLICATION_JSON).body(oauth2TokenResponse));
547+
548+
//UAA retrieves user info using an access token
549+
mockUaaServer.expect(requestTo(config.getUserInfoUrl().toString()))
550+
.andRespond(withStatus(OK).contentType(APPLICATION_JSON).body(userInfoResponse));
551+
552+
IdentityProvider<RawExternalOAuthIdentityProviderDefinition> identityProvider = getOauth20Provider();
553+
identityProvider.getConfig().setResponseType("code");
554+
reset(provisioning);
555+
when(provisioning.retrieveByOrigin(eq(ORIGIN), anyString())).thenReturn(identityProvider);
556+
557+
addTheUserOnAuth();
558+
559+
Authentication authentication = externalOAuthAuthenticationManager.authenticate(xCodeToken);
560+
assertThat(authentication).isNotNull();
561+
assertThat(authentication.getPrincipal()).isInstanceOf(UaaPrincipal.class);
562+
UaaPrincipal principal = (UaaPrincipal) authentication.getPrincipal();
563+
assertThat(principal).isNotNull();
564+
assertThat(principal.getName()).isEqualTo("octocat");
565+
assertThat(principal.getEmail()).isEqualTo("[email protected]");
566+
mockUaaServer.verify();
567+
568+
}
569+
518570
@Test
519571
void clientAuthInBody_is_used() {
520572
config.setClientAuthInBody(true);
@@ -1291,6 +1343,31 @@ private IdentityProvider<OIDCIdentityProviderDefinition> getProvider() {
12911343
return identityProvider;
12921344
}
12931345

1346+
private IdentityProvider<RawExternalOAuthIdentityProviderDefinition> getOauth20Provider() throws Exception {
1347+
RawExternalOAuthIdentityProviderDefinition config = new RawExternalOAuthIdentityProviderDefinition()
1348+
.setAuthUrl(URI.create("http://localhost/oauth/authorize").toURL())
1349+
.setTokenUrl(URI.create("http://localhost/oauth/token").toURL())
1350+
.setIssuer("http://localhost/oauth/token")
1351+
.setShowLinkText(true)
1352+
.setLinkText("My oauth20 Provider")
1353+
.setRelyingPartyId("identity")
1354+
.setRelyingPartySecret("identitysecret")
1355+
.setUserInfoUrl(URI.create("http://localhost/userinfo").toURL())
1356+
.setTokenKey(PUBLIC_KEY);
1357+
config.setExternalGroupsWhitelist(Collections.singletonList("*"));
1358+
attributeMappings.put(USER_NAME_ATTRIBUTE_NAME, "login");
1359+
config.setAttributeMappings(attributeMappings);
1360+
config.setResponseType("code");
1361+
1362+
IdentityProvider<RawExternalOAuthIdentityProviderDefinition> identityProvider = new IdentityProvider<>();
1363+
identityProvider.setName("my oauth20 provider");
1364+
identityProvider.setIdentityZoneId(OriginKeys.UAA);
1365+
identityProvider.setType(OAUTH20);
1366+
identityProvider.setConfig(config);
1367+
identityProvider.setOriginKey(ORIGIN);
1368+
return identityProvider;
1369+
}
1370+
12941371
private void testTokenHasAuthoritiesFromIdTokenRoles() {
12951372
attributeMappings.put(GROUP_ATTRIBUTE_NAME, "scope");
12961373
mockToken();

server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerTest.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.cloudfoundry.identity.uaa.provider.IdentityProvider;
3131
import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning;
3232
import org.cloudfoundry.identity.uaa.provider.OIDCIdentityProviderDefinition;
33+
import org.cloudfoundry.identity.uaa.provider.RawExternalOAuthIdentityProviderDefinition;
3334
import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMember;
3435
import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimGroupExternalMembershipManager;
3536
import org.cloudfoundry.identity.uaa.user.UaaUser;
@@ -40,6 +41,7 @@
4041
import org.cloudfoundry.identity.uaa.util.UaaTokenUtils;
4142
import org.cloudfoundry.identity.uaa.zone.IdentityZone;
4243
import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
44+
import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager;
4345
import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManagerImpl;
4446
import org.junit.jupiter.api.AfterEach;
4547
import org.junit.jupiter.api.BeforeEach;
@@ -83,6 +85,7 @@
8385
import static org.assertj.core.api.Assertions.assertThatNoException;
8486
import static org.assertj.core.api.Assertions.assertThatThrownBy;
8587
import static org.assertj.core.api.Assertions.fail;
88+
import static org.cloudfoundry.identity.uaa.constants.OriginKeys.OAUTH20;
8689
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.AUD;
8790
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.EMAIL;
8891
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.EXPIRY_IN_SECONDS;
@@ -1083,6 +1086,49 @@ void oidcPasswordGrantWithPrompts() throws MalformedURLException, JOSEException
10831086
assertThat(headers).doesNotContainKey("X-Forwarded-For");
10841087
}
10851088

1089+
@Test
1090+
void oauth20AuthorizationFlowWithUserInfo() throws Exception {
1091+
1092+
final ExternalOAuthCodeToken codeToken = new ExternalOAuthCodeToken("thecode", "some-origin", "http://google.com", null, null, "signedrequest");
1093+
final RestTemplate restTemplate = mock(RestTemplate.class);
1094+
final ResponseEntity<Map<String, Object>> responseEntity = mock(ResponseEntity.class);
1095+
when(responseEntity.getStatusCode()).thenReturn(HttpStatus.OK);
1096+
when(responseEntity.getBody()).thenReturn(Collections.emptyMap());
1097+
when(restTemplate.exchange(any(URI.class), eq(HttpMethod.GET), any(HttpEntity.class), any(ParameterizedTypeReference.class))).thenReturn(responseEntity);
1098+
authManager = new ExternalOAuthAuthenticationManager(
1099+
identityProviderProvisioning,
1100+
mock(IdentityZoneManager.class),
1101+
restTemplate,
1102+
restTemplate,
1103+
tokenEndpointBuilder,
1104+
new KeyInfoService("http://uaa.example.com"),
1105+
null
1106+
) {
1107+
@Override
1108+
protected <T extends AbstractExternalOAuthIdentityProviderDefinition<T>> String getTokenFromCode(
1109+
ExternalOAuthCodeToken codeToken,
1110+
IdentityProvider<T> config
1111+
) {
1112+
return "opaque-access-token";
1113+
}
1114+
1115+
@Override
1116+
public RestTemplate getRestTemplate(AbstractExternalOAuthIdentityProviderDefinition config) {
1117+
return restTemplate;
1118+
}
1119+
};
1120+
final RawExternalOAuthIdentityProviderDefinition config = new RawExternalOAuthIdentityProviderDefinition();
1121+
config.setResponseType("code");
1122+
config.setUserInfoUrl(URI.create("https://some.metadata.url/userinfo").toURL());
1123+
final IdentityProvider<AbstractExternalOAuthIdentityProviderDefinition> idp = new IdentityProvider<>();
1124+
idp.setType(OAUTH20);
1125+
idp.setConfig(config);
1126+
authManager.getClaimsFromToken(codeToken, idp);
1127+
assertThat(codeToken.getIdToken()).isNull();
1128+
assertThat(codeToken.getAccessToken()).isEqualTo("opaque-access-token");
1129+
1130+
}
1131+
10861132
private static void assertAuthorizationHeaderIsSetAndStartsWithBasic(final HttpHeaders headers) {
10871133
assertThat(headers).containsKey("Authorization");
10881134
final List<String> authorizationHeaders = headers.get("Authorization");

server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthProviderConfiguratorTests.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,4 +585,30 @@ void idpWithAliasExistsInZone(final boolean resultFromDelegate) {
585585
when(mockIdentityProviderProvisioning.idpWithAliasExistsInZone(zoneId)).thenReturn(resultFromDelegate);
586586
assertThat(configurator.idpWithAliasExistsInZone(zoneId)).isEqualTo(resultFromDelegate);
587587
}
588+
589+
@Test
590+
void getIdpAuthenticationUrl_verifyNonceAndStateLength() {
591+
// Mock state generation (22 characters)
592+
String stateValue = "a".repeat(22); // exactly 22 characters
593+
when(mockUaaRandomStringUtil.getSecureRandom(22)).thenReturn(stateValue);
594+
595+
// Mock code verifier generation (128 characters) - needed for PKCE
596+
String codeVerifierValue = "b".repeat(128);
597+
when(mockUaaRandomStringUtil.getSecureRandom(128)).thenReturn(codeVerifierValue);
598+
599+
String authzUri = configurator.getIdpAuthenticationUrl(oidc, "alias", mockHttpServletRequest);
600+
Map<String, String> queryParams =
601+
UriComponentsBuilder.fromUriString(authzUri).build().getQueryParams().toSingleValueMap();
602+
603+
// Verify state is at least 22 characters
604+
assertThat(queryParams.get("state"))
605+
.as("State parameter should be at least 22 characters")
606+
.hasSizeGreaterThanOrEqualTo(22);
607+
608+
// Verify nonce exists and has expected length (RandomValueStringGenerator(22) generates 22 chars)
609+
assertThat(queryParams.get("nonce"))
610+
.as("Nonce parameter should be at least 22 characters")
611+
.isNotNull()
612+
.hasSizeGreaterThanOrEqualTo(22);
613+
}
588614
}

0 commit comments

Comments
 (0)