Skip to content

Commit de24a2b

Browse files
committed
Add operation to update SAML provider configs. (#424)
1 parent 7732833 commit de24a2b

File tree

6 files changed

+341
-15
lines changed

6 files changed

+341
-15
lines changed

src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1129,6 +1129,7 @@ protected OidcProviderConfig execute() throws FirebaseAuthException {
11291129
* @param request A non-null {@link OidcProviderConfig.UpdateRequest} instance.
11301130
* @return A {@link OidcProviderConfig} instance corresponding to the updated provider config.
11311131
* @throws NullPointerException if the provided update request is null.
1132+
* @throws IllegalArgumentException If the provided update request is invalid.
11321133
* @throws FirebaseAuthException if an error occurs while updating the provider config.
11331134
*/
11341135
public OidcProviderConfig updateOidcProviderConfig(
@@ -1143,6 +1144,8 @@ public OidcProviderConfig updateOidcProviderConfig(
11431144
* @return An {@code ApiFuture} which will complete successfully with a {@link OidcProviderConfig}
11441145
* instance corresponding to the updated provider config. If an error occurs while updating
11451146
* the provider config, the future throws a {@link FirebaseAuthException}.
1147+
* @throws NullPointerException if the provided update request is null.
1148+
* @throws IllegalArgumentException If the provided update request is invalid.
11461149
*/
11471150
public ApiFuture<OidcProviderConfig> updateOidcProviderConfigAsync(
11481151
@NonNull OidcProviderConfig.UpdateRequest request) {
@@ -1153,6 +1156,8 @@ private CallableOperation<OidcProviderConfig, FirebaseAuthException> updateOidcP
11531156
final OidcProviderConfig.UpdateRequest request) {
11541157
checkNotDestroyed();
11551158
checkNotNull(request, "Update request must not be null.");
1159+
checkArgument(!request.getProperties().isEmpty(),
1160+
"Update request must have at least one property set.");
11561161
final FirebaseUserManager userManager = getUserManager();
11571162
return new CallableOperation<OidcProviderConfig, FirebaseAuthException>() {
11581163
@Override
@@ -1379,6 +1384,51 @@ protected SamlProviderConfig execute() throws FirebaseAuthException {
13791384
};
13801385
}
13811386

1387+
/**
1388+
* Updates an existing SAML Auth provider config with the attributes contained in the specified
1389+
* {@link OidcProviderConfig.UpdateRequest}.
1390+
*
1391+
* @param request A non-null {@link SamlProviderConfig.UpdateRequest} instance.
1392+
* @return A {@link SamlProviderConfig} instance corresponding to the updated provider config.
1393+
* @throws NullPointerException if the provided update request is null.
1394+
* @throws IllegalArgumentException If the provided update request is invalid.
1395+
* @throws FirebaseAuthException if an error occurs while updating the provider config.
1396+
*/
1397+
public SamlProviderConfig updateSamlProviderConfig(
1398+
@NonNull SamlProviderConfig.UpdateRequest request) throws FirebaseAuthException {
1399+
return updateSamlProviderConfigOp(request).call();
1400+
}
1401+
1402+
/**
1403+
* Similar to {@link #updateSamlProviderConfig} but performs the operation asynchronously.
1404+
*
1405+
* @param request A non-null {@link SamlProviderConfig.UpdateRequest} instance.
1406+
* @return An {@code ApiFuture} which will complete successfully with a {@link SamlProviderConfig}
1407+
* instance corresponding to the updated provider config. If an error occurs while updating
1408+
* the provider config, the future throws a {@link FirebaseAuthException}.
1409+
* @throws NullPointerException if the provided update request is null.
1410+
* @throws IllegalArgumentException If the provided update request is invalid.
1411+
*/
1412+
public ApiFuture<SamlProviderConfig> updateSamlProviderConfigAsync(
1413+
@NonNull SamlProviderConfig.UpdateRequest request) {
1414+
return updateSamlProviderConfigOp(request).callAsync(firebaseApp);
1415+
}
1416+
1417+
private CallableOperation<SamlProviderConfig, FirebaseAuthException> updateSamlProviderConfigOp(
1418+
final SamlProviderConfig.UpdateRequest request) {
1419+
checkNotDestroyed();
1420+
checkNotNull(request, "Update request must not be null.");
1421+
checkArgument(!request.getProperties().isEmpty(),
1422+
"Update request must have at least one property set.");
1423+
final FirebaseUserManager userManager = getUserManager();
1424+
return new CallableOperation<SamlProviderConfig, FirebaseAuthException>() {
1425+
@Override
1426+
protected SamlProviderConfig execute() throws FirebaseAuthException {
1427+
return userManager.updateSamlProviderConfig(request);
1428+
}
1429+
};
1430+
}
1431+
13821432
/**
13831433
* Gets the SAML provider Auth config corresponding to the specified provider ID.
13841434
*

src/main/java/com/google/firebase/auth/FirebaseUserManager.java

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ Tenant updateTenant(Tenant.UpdateRequest request) throws FirebaseAuthException {
306306
// CallableOperation.
307307
checkArgument(!properties.isEmpty(), "Tenant update must have at least one property set");
308308
GenericUrl url = new GenericUrl(tenantMgtBaseUrl + getTenantUrlSuffix(request.getTenantId()));
309-
url.put("updateMask", generateMask(properties));
309+
url.put("updateMask", Joiner.on(",").join(generateMask(properties)));
310310
return sendRequest("PATCH", url, properties, Tenant.class);
311311
}
312312

@@ -384,16 +384,21 @@ SamlProviderConfig createSamlProviderConfig(
384384
OidcProviderConfig updateOidcProviderConfig(OidcProviderConfig.UpdateRequest request)
385385
throws FirebaseAuthException {
386386
Map<String, Object> properties = request.getProperties();
387-
// TODO(micahstairs): Move this check so that argument validation happens outside the
388-
// CallableOperation.
389-
checkArgument(!properties.isEmpty(),
390-
"Provider config update must have at least one property set.");
391387
GenericUrl url =
392388
new GenericUrl(idpConfigMgtBaseUrl + getOidcUrlSuffix(request.getProviderId()));
393-
url.put("updateMask", generateMask(properties));
389+
url.put("updateMask", Joiner.on(",").join(generateMask(properties)));
394390
return sendRequest("PATCH", url, properties, OidcProviderConfig.class);
395391
}
396392

393+
SamlProviderConfig updateSamlProviderConfig(SamlProviderConfig.UpdateRequest request)
394+
throws FirebaseAuthException {
395+
Map<String, Object> properties = request.getProperties();
396+
GenericUrl url =
397+
new GenericUrl(idpConfigMgtBaseUrl + getSamlUrlSuffix(request.getProviderId()));
398+
url.put("updateMask", Joiner.on(",").join(generateMask(properties)));
399+
return sendRequest("PATCH", url, properties, SamlProviderConfig.class);
400+
}
401+
397402
OidcProviderConfig getOidcProviderConfig(String providerId) throws FirebaseAuthException {
398403
GenericUrl url = new GenericUrl(idpConfigMgtBaseUrl + getOidcUrlSuffix(providerId));
399404
return sendRequest("GET", url, null, OidcProviderConfig.class);
@@ -434,12 +439,19 @@ void deleteSamlProviderConfig(String providerId) throws FirebaseAuthException {
434439
sendRequest("DELETE", url, null, GenericJson.class);
435440
}
436441

437-
private static String generateMask(Map<String, Object> properties) {
438-
// This implementation does not currently handle the case of nested properties. This is fine
439-
// since we do not currently generate masks for any properties with nested values. When it
440-
// comes time to implement this, we can check if a property has nested properties by checking
441-
// if it is an instance of the Map class.
442-
return Joiner.on(",").join(ImmutableSortedSet.copyOf(properties.keySet()));
442+
private static Set<String> generateMask(Map<String, Object> properties) {
443+
ImmutableSortedSet.Builder<String> maskBuilder = ImmutableSortedSet.naturalOrder();
444+
for (Map.Entry<String, Object> entry : properties.entrySet()) {
445+
if (entry.getValue() instanceof Map) {
446+
Set<String> childMask = generateMask((Map<String, Object>) entry.getValue());
447+
for (String childProperty : childMask) {
448+
maskBuilder.add(entry.getKey() + "." + childProperty);
449+
}
450+
} else {
451+
maskBuilder.add(entry.getKey());
452+
}
453+
}
454+
return maskBuilder.build();
443455
}
444456

445457
private static String getTenantUrlSuffix(String tenantId) {

src/main/java/com/google/firebase/auth/SamlProviderConfig.java

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,16 @@ public String getCallbackUrl() {
7171
return (String) spConfig.get("callbackUri");
7272
}
7373

74+
/**
75+
* Returns a new {@link UpdateRequest}, which can be used to update the attributes of this
76+
* provider config.
77+
*
78+
* @return a non-null {@link UpdateRequest} instance.
79+
*/
80+
public UpdateRequest updateRequest() {
81+
return new UpdateRequest(getProviderId());
82+
}
83+
7484
static void checkSamlProviderId(String providerId) {
7585
checkArgument(!Strings.isNullOrEmpty(providerId), "Provider ID must not be null or empty.");
7686
checkArgument(providerId.startsWith("saml."),
@@ -201,4 +211,106 @@ CreateRequest getThis() {
201211
return this;
202212
}
203213
}
214+
215+
/**
216+
* A specification class for updating an existing SAML Auth provider.
217+
*
218+
* <p>An instance of this class can be obtained via a {@link SamlProviderConfig} object, or from
219+
* a provider ID string. Specify the changes to be made to the provider config by calling the
220+
* various setter methods available in this class.
221+
*/
222+
public static final class UpdateRequest extends AbstractUpdateRequest<UpdateRequest> {
223+
/**
224+
* Creates a new {@link UpdateRequest}, which can be used to updates an existing SAML Auth
225+
* provider.
226+
*
227+
* <p>The returned object should be passed to
228+
* {@link AbstractFirebaseAuth#updateSamlProviderConfig(UpdateRequest)} to update the provider
229+
* information persistently.
230+
*
231+
* @param providerId a non-null, non-empty provider ID string.
232+
* @throws IllegalArgumentException If the provider ID is null or empty, or is not prefixed with
233+
* 'saml.'.
234+
*/
235+
public UpdateRequest(String providerId) {
236+
super(providerId);
237+
checkSamlProviderId(providerId);
238+
}
239+
240+
/**
241+
* Sets the IDP entity ID for the existing provider.
242+
*
243+
* @param idpEntityId A non-null, non-empty IDP entity ID string.
244+
* @throws IllegalArgumentException If the IDP entity ID is null or empty.
245+
*/
246+
public UpdateRequest setIdpEntityId(String idpEntityId) {
247+
checkArgument(!Strings.isNullOrEmpty(idpEntityId),
248+
"IDP entity ID must not be null or empty.");
249+
ensureNestedMap(properties, "idpConfig").put("idpEntityId", idpEntityId);
250+
return this;
251+
}
252+
253+
/**
254+
* Sets the SSO URL for the existing provider.
255+
*
256+
* @param ssoUrl A non-null, non-empty SSO URL string.
257+
* @throws IllegalArgumentException If the SSO URL is null or empty, or if the format is
258+
* invalid.
259+
*/
260+
public UpdateRequest setSsoUrl(String ssoUrl) {
261+
checkArgument(!Strings.isNullOrEmpty(ssoUrl), "SSO URL must not be null or empty.");
262+
assertValidUrl(ssoUrl);
263+
ensureNestedMap(properties, "idpConfig").put("ssoUrl", ssoUrl);
264+
return this;
265+
}
266+
267+
/**
268+
* Adds a x509 certificate to the existing provider.
269+
*
270+
* @param x509Certificate A non-null, non-empty x509 certificate string.
271+
* @throws IllegalArgumentException If the x509 certificate is null or empty.
272+
*/
273+
public UpdateRequest addX509Certificate(String x509Certificate) {
274+
checkArgument(!Strings.isNullOrEmpty(x509Certificate),
275+
"The x509 certificate must not be null or empty.");
276+
Map<String, Object> idpConfigProperties = ensureNestedMap(properties, "idpConfig");
277+
List<Object> x509Certificates = ensureNestedList(idpConfigProperties, "idpCertificates");
278+
x509Certificates.add(ImmutableMap.<String, Object>of("x509Certificate", x509Certificate));
279+
return this;
280+
}
281+
282+
// TODO(micahstairs): Add 'addAllX509Certificates' method.
283+
284+
/**
285+
* Sets the RP entity ID for the existing provider.
286+
*
287+
* @param rpEntityId A non-null, non-empty RP entity ID string.
288+
* @throws IllegalArgumentException If the RP entity ID is null or empty.
289+
*/
290+
public UpdateRequest setRpEntityId(String rpEntityId) {
291+
checkArgument(!Strings.isNullOrEmpty(rpEntityId), "RP entity ID must not be null or empty.");
292+
ensureNestedMap(properties, "spConfig").put("spEntityId", rpEntityId);
293+
return this;
294+
}
295+
296+
/**
297+
* Sets the callback URL for the exising provider.
298+
*
299+
* @param callbackUrl A non-null, non-empty callback URL string.
300+
* @throws IllegalArgumentException If the callback URL is null or empty, or if the format is
301+
* invalid.
302+
*/
303+
public UpdateRequest setCallbackUrl(String callbackUrl) {
304+
checkArgument(!Strings.isNullOrEmpty(callbackUrl), "Callback URL must not be null or empty.");
305+
assertValidUrl(callbackUrl);
306+
ensureNestedMap(properties, "spConfig").put("callbackUri", callbackUrl);
307+
return this;
308+
}
309+
310+
// TODO(micahstairs): Add 'setRequestSigningEnabled' method.
311+
312+
UpdateRequest getThis() {
313+
return this;
314+
}
315+
}
204316
}

src/test/java/com/google/firebase/auth/FirebaseAuthIT.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -809,7 +809,17 @@ public void testSamlProviderConfigLifecycle() throws Exception {
809809
assertEquals("RP_ENTITY_ID", config.getRpEntityId());
810810
assertEquals("https://projectId.firebaseapp.com/__/auth/handler", config.getCallbackUrl());
811811

812-
// TODO(micahstairs): Once implemented, add tests for updating the SAML provider config.
812+
// Update provider config
813+
SamlProviderConfig.UpdateRequest updateRequest =
814+
new SamlProviderConfig.UpdateRequest(providerId)
815+
.setDisplayName("NewDisplayName")
816+
.setEnabled(false)
817+
.addX509Certificate("certificate");
818+
config = auth.updateSamlProviderConfigAsync(updateRequest).get();
819+
assertEquals(providerId, config.getProviderId());
820+
assertEquals("NewDisplayName", config.getDisplayName());
821+
assertFalse(config.isEnabled());
822+
assertEquals(ImmutableList.of("certificate"), config.getX509Certificates());
813823

814824
// Delete provider config
815825
temporaryProviderConfig.deleteSamlProviderConfig(providerId);

0 commit comments

Comments
 (0)