Skip to content

Commit

Permalink
feature: filter IdP retrival (#2882)
Browse files Browse the repository at this point in the history
* WIP: idp secret

* feature: delete secret on existing IdP

- allow to delete a relyingPartySecret on IdP
- Filter IdP list by origin
- Return the auth_method to show current configured client authentication method

* Documentation

* fix names

* sonar

* sonar

* Add patch call to change a secret from an external IdP

* Alias handling

* Sonar refactorings

See
https://sonarcloud.io/code?id=cloudfoundry-identity-parent&selected=cloudfoundry-identity-parent%3Aserver%2Fsrc%2Fmain%2Fjava%2Forg%2Fcloudfoundry%2Fidentity%2Fuaa%2Fprovider%2FIdentityProviderEndpoints.java

* remove dead code

* more sonar smell fixes

* no log change

* revert and allow test endpoint again

* warnings

* rebase

* rebase

* test setup

* rebase

* review
  • Loading branch information
strehle committed Jun 4, 2024
1 parent 08a5c80 commit ac9602c
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import org.cloudfoundry.identity.uaa.scim.ScimGroupProvisioning;
import org.cloudfoundry.identity.uaa.util.JsonUtils;
import org.cloudfoundry.identity.uaa.util.ObjectUtils;
import org.cloudfoundry.identity.uaa.util.UaaStringUtils;
import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager;
import org.opensaml.saml2.metadata.provider.MetadataProviderException;
import org.slf4j.Logger;
Expand Down Expand Up @@ -293,10 +294,19 @@ public ResponseEntity<IdentityProviderStatus> updateIdentityProviderStatus(@Path
}

@GetMapping()
public ResponseEntity<List<IdentityProvider>> retrieveIdentityProviders(@RequestParam(value = "active_only", required = false) String activeOnly, @RequestParam(required = false, defaultValue = "false") boolean rawConfig) {
public ResponseEntity<List<IdentityProvider>> retrieveIdentityProviders(
@RequestParam(value = "active_only", required = false) String activeOnly,
@RequestParam(required = false, defaultValue = "false") boolean rawConfig,
@RequestParam(required = false, defaultValue = "") String originKey)
{
boolean retrieveActiveOnly = Boolean.parseBoolean(activeOnly);
List<IdentityProvider> identityProviderList = identityProviderProvisioning.retrieveAll(retrieveActiveOnly, identityZoneManager.getCurrentIdentityZoneId());
for(IdentityProvider idp : identityProviderList) {
List<IdentityProvider> identityProviderList;
if (UaaStringUtils.isNotEmpty(originKey)) {
identityProviderList = List.of(identityProviderProvisioning.retrieveByOrigin(originKey, identityZoneManager.getCurrentIdentityZoneId()));
} else {
identityProviderList = identityProviderProvisioning.retrieveAll(retrieveActiveOnly, identityZoneManager.getCurrentIdentityZoneId());
}
for(IdentityProvider<?> idp : identityProviderList) {
idp.setSerializeConfigRaw(rawConfig);
setAuthMethod(idp);
redactSensitiveData(idp);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ void patch_bind_password_non_ldap() {
void retrieve_all_providers_redacts_data() {
when(mockIdentityProviderProvisioning.retrieveAll(anyBoolean(), anyString()))
.thenReturn(Arrays.asList(getLdapDefinition(), getExternalOAuthProvider()));
ResponseEntity<List<IdentityProvider>> ldapList = identityProviderEndpoints.retrieveIdentityProviders("false", true);
ResponseEntity<List<IdentityProvider>> ldapList = identityProviderEndpoints.retrieveIdentityProviders("false", true, "");
assertNotNull(ldapList);
assertNotNull(ldapList.getBody());
assertEquals(2, ldapList.getBody().size());
Expand All @@ -313,6 +313,22 @@ void retrieve_all_providers_redacts_data() {
assertNull(oauth.getConfig().getRelyingPartySecret());
}

@Test
void retrieve_by_origin_providers_redacts_data() {
when(mockIdentityProviderProvisioning.retrieveByOrigin(anyString(), anyString()))
.thenReturn(getExternalOAuthProvider());
ResponseEntity<List<IdentityProvider>> puppyList = identityProviderEndpoints.retrieveIdentityProviders("false", true, "puppy");
assertNotNull(puppyList);
assertNotNull(puppyList.getBody());
assertEquals(1, puppyList.getBody().size());
IdentityProvider<OIDCIdentityProviderDefinition> oidc = puppyList.getBody().get(0);
assertNotNull(oidc);
assertNotNull(oidc.getConfig());
assertTrue(oidc.getConfig() instanceof AbstractExternalOAuthIdentityProviderDefinition);
assertNull(oidc.getConfig().getRelyingPartySecret());
assertEquals(ClientAuthentication.CLIENT_SECRET_BASIC, oidc.getConfig().getAuthMethod());
}

@Test
void update_ldap_provider_patches_password() throws Exception {
IdentityProvider<LdapIdentityProviderDefinition> provider = retrieve_ldap_provider_by_id("id");
Expand Down
25 changes: 25 additions & 0 deletions uaa/slateCustomizations/source/index.html.md.erb
Original file line number Diff line number Diff line change
Expand Up @@ -1115,6 +1115,31 @@ _Error Codes_
|------------|-----------------------------------------------------------------------|
| 403 | Forbidden - Insufficient scope |

## Retrieve with Filtering

<%= render('IdentityProviderEndpointDocs/getFilteredIdentityProviders/curl-request.md') %>
<%= render('IdentityProviderEndpointDocs/getFilteredIdentityProviders/http-request.md') %>
<%= render('IdentityProviderEndpointDocs/getFilteredIdentityProviders/http-response.md') %>

_Request Headers_

<%= render('IdentityProviderEndpointDocs/getFilteredIdentityProviders/request-headers.md') %>

_Request Parameters_

<%= render('IdentityProviderEndpointDocs/getFilteredIdentityProviders/request-parameters.md') %>

_Response Fields_

<%= render('IdentityProviderEndpointDocs/getFilteredIdentityProviders/response-fields.md') %>

_Error Codes_

| Error Code | Description |
|------------|-----------------------------------------------------------------------|
| 403 | Forbidden - Insufficient scope |


## Retrieve

<%= render('IdentityProviderEndpointDocs/getIdentityProvider/curl-request.md') %>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -967,6 +967,47 @@ void getAllIdentityProviders() throws Exception {
responseFields));
}

@Test
void getFilteredIdentityProviders() throws Exception {
Snippet responseFields = responseFields(
fieldWithPath("[].type").description("Type of the identity provider."),
fieldWithPath("[].originKey").description("Unique identifier for the identity provider."),
fieldWithPath("[].name").description(NAME_DESC),
fieldWithPath("[].config").description(CONFIG_DESCRIPTION),
fieldWithPath("[]." + FIELD_ALIAS_ID).description(ALIAS_ID_DESC).attributes(key("constraints").value("Optional")).optional().type(STRING),
fieldWithPath("[]." + FIELD_ALIAS_ZID).description(ALIAS_ZID_DESC).attributes(key("constraints").value("Optional")).optional().type(STRING),

fieldWithPath("[].version").description(VERSION_DESC),
fieldWithPath("[].active").description(ACTIVE_DESC),

fieldWithPath("[].id").description(ID_DESC),
fieldWithPath("[].identityZoneId").description(IDENTITY_ZONE_ID_DESC),
fieldWithPath("[].created").description(CREATED_DESC),
fieldWithPath("[].last_modified").description(LAST_MODIFIED_DESC)
);

mockMvc.perform(get("/identity-providers")
.param("rawConfig", "false")
.param("active_only", "false")
.param("originKey", "my-oauth2-provider")
.header("Authorization", "Bearer " + adminToken)
.contentType(APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(document("{ClassName}/{methodName}",
preprocessResponse(prettyPrint()),
requestHeaders(
headerWithName("Authorization").description("Bearer token containing `zones.<zone id>.admin` or `zones.<zone id>.idps.read` or `uaa.admin` or `idps.read` (only in the same zone that you are a user of)"),
headerWithName("X-Identity-Zone-Id").description("May include this header to administer another zone if using `zones.<zoneId>.admin` or `zones.<zone id>.idps.read` or `uaa.admin` scope against the default UAA zone.").optional(),
IDENTITY_ZONE_SUBDOMAIN_HEADER
),
requestParameters(
parameterWithName("rawConfig").optional("false").type(BOOLEAN).description("Flag indicating whether the response should use raw, unescaped JSON for the `config` field of the IDP, rather than the default behavior of encoding the JSON as a string."),
parameterWithName("active_only").optional("false").type(BOOLEAN).description("Flag indicating whether only active IdPs should be returned or all."),
parameterWithName("originKey").optional(null).type(STRING).description("<small><mark>UAA 77.10.0</mark></small> Return only IdPs with specific origin.")
),
responseFields));
}

@Test
void getIdentityProvider() throws Exception {
IdentityProvider identityProvider = JsonUtils.readValue(mockMvc.perform(post("/identity-providers")
Expand Down

0 comments on commit ac9602c

Please sign in to comment.