Skip to content

Commit c3a08b4

Browse files
authored
Add operation to update SAML provider configs. (#424)
1 parent 1e36411 commit c3a08b4

File tree

6 files changed

+342
-15
lines changed

6 files changed

+342
-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
@@ -992,6 +992,7 @@ protected OidcProviderConfig execute() throws FirebaseAuthException {
992992
* @param request A non-null {@link OidcProviderConfig.UpdateRequest} instance.
993993
* @return A {@link OidcProviderConfig} instance corresponding to the updated provider config.
994994
* @throws NullPointerException if the provided update request is null.
995+
* @throws IllegalArgumentException If the provided update request is invalid.
995996
* @throws FirebaseAuthException if an error occurs while updating the provider config.
996997
*/
997998
public OidcProviderConfig updateOidcProviderConfig(
@@ -1006,6 +1007,8 @@ public OidcProviderConfig updateOidcProviderConfig(
10061007
* @return An {@code ApiFuture} which will complete successfully with a {@link OidcProviderConfig}
10071008
* instance corresponding to the updated provider config. If an error occurs while updating
10081009
* the provider config, the future throws a {@link FirebaseAuthException}.
1010+
* @throws NullPointerException if the provided update request is null.
1011+
* @throws IllegalArgumentException If the provided update request is invalid.
10091012
*/
10101013
public ApiFuture<OidcProviderConfig> updateOidcProviderConfigAsync(
10111014
@NonNull OidcProviderConfig.UpdateRequest request) {
@@ -1016,6 +1019,8 @@ private CallableOperation<OidcProviderConfig, FirebaseAuthException> updateOidcP
10161019
final OidcProviderConfig.UpdateRequest request) {
10171020
checkNotDestroyed();
10181021
checkNotNull(request, "Update request must not be null.");
1022+
checkArgument(!request.getProperties().isEmpty(),
1023+
"Update request must have at least one property set.");
10191024
final FirebaseUserManager userManager = getUserManager();
10201025
return new CallableOperation<OidcProviderConfig, FirebaseAuthException>() {
10211026
@Override
@@ -1242,6 +1247,51 @@ protected SamlProviderConfig execute() throws FirebaseAuthException {
12421247
};
12431248
}
12441249

1250+
/**
1251+
* Updates an existing SAML Auth provider config with the attributes contained in the specified
1252+
* {@link OidcProviderConfig.UpdateRequest}.
1253+
*
1254+
* @param request A non-null {@link SamlProviderConfig.UpdateRequest} instance.
1255+
* @return A {@link SamlProviderConfig} instance corresponding to the updated provider config.
1256+
* @throws NullPointerException if the provided update request is null.
1257+
* @throws IllegalArgumentException If the provided update request is invalid.
1258+
* @throws FirebaseAuthException if an error occurs while updating the provider config.
1259+
*/
1260+
public SamlProviderConfig updateSamlProviderConfig(
1261+
@NonNull SamlProviderConfig.UpdateRequest request) throws FirebaseAuthException {
1262+
return updateSamlProviderConfigOp(request).call();
1263+
}
1264+
1265+
/**
1266+
* Similar to {@link #updateSamlProviderConfig} but performs the operation asynchronously.
1267+
*
1268+
* @param request A non-null {@link SamlProviderConfig.UpdateRequest} instance.
1269+
* @return An {@code ApiFuture} which will complete successfully with a {@link SamlProviderConfig}
1270+
* instance corresponding to the updated provider config. If an error occurs while updating
1271+
* the provider config, the future throws a {@link FirebaseAuthException}.
1272+
* @throws NullPointerException if the provided update request is null.
1273+
* @throws IllegalArgumentException If the provided update request is invalid.
1274+
*/
1275+
public ApiFuture<SamlProviderConfig> updateSamlProviderConfigAsync(
1276+
@NonNull SamlProviderConfig.UpdateRequest request) {
1277+
return updateSamlProviderConfigOp(request).callAsync(firebaseApp);
1278+
}
1279+
1280+
private CallableOperation<SamlProviderConfig, FirebaseAuthException> updateSamlProviderConfigOp(
1281+
final SamlProviderConfig.UpdateRequest request) {
1282+
checkNotDestroyed();
1283+
checkNotNull(request, "Update request must not be null.");
1284+
checkArgument(!request.getProperties().isEmpty(),
1285+
"Update request must have at least one property set.");
1286+
final FirebaseUserManager userManager = getUserManager();
1287+
return new CallableOperation<SamlProviderConfig, FirebaseAuthException>() {
1288+
@Override
1289+
protected SamlProviderConfig execute() throws FirebaseAuthException {
1290+
return userManager.updateSamlProviderConfig(request);
1291+
}
1292+
};
1293+
}
1294+
12451295
/**
12461296
* Gets the SAML provider Auth config corresponding to the specified provider ID.
12471297
*

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

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import java.io.IOException;
5555
import java.util.List;
5656
import java.util.Map;
57+
import java.util.Set;
5758

5859
/**
5960
* FirebaseUserManager provides methods for interacting with the Google Identity Toolkit via its
@@ -256,7 +257,7 @@ Tenant updateTenant(Tenant.UpdateRequest request) throws FirebaseAuthException {
256257
// CallableOperation.
257258
checkArgument(!properties.isEmpty(), "tenant update must have at least one property set");
258259
GenericUrl url = new GenericUrl(tenantMgtBaseUrl + getTenantUrlSuffix(request.getTenantId()));
259-
url.put("updateMask", generateMask(properties));
260+
url.put("updateMask", Joiner.on(",").join(generateMask(properties)));
260261
return sendRequest("PATCH", url, properties, Tenant.class);
261262
}
262263

@@ -334,16 +335,21 @@ SamlProviderConfig createSamlProviderConfig(
334335
OidcProviderConfig updateOidcProviderConfig(OidcProviderConfig.UpdateRequest request)
335336
throws FirebaseAuthException {
336337
Map<String, Object> properties = request.getProperties();
337-
// TODO(micahstairs): Move this check so that argument validation happens outside the
338-
// CallableOperation.
339-
checkArgument(!properties.isEmpty(),
340-
"Provider config update must have at least one property set.");
341338
GenericUrl url =
342339
new GenericUrl(idpConfigMgtBaseUrl + getOidcUrlSuffix(request.getProviderId()));
343-
url.put("updateMask", generateMask(properties));
340+
url.put("updateMask", Joiner.on(",").join(generateMask(properties)));
344341
return sendRequest("PATCH", url, properties, OidcProviderConfig.class);
345342
}
346343

344+
SamlProviderConfig updateSamlProviderConfig(SamlProviderConfig.UpdateRequest request)
345+
throws FirebaseAuthException {
346+
Map<String, Object> properties = request.getProperties();
347+
GenericUrl url =
348+
new GenericUrl(idpConfigMgtBaseUrl + getSamlUrlSuffix(request.getProviderId()));
349+
url.put("updateMask", Joiner.on(",").join(generateMask(properties)));
350+
return sendRequest("PATCH", url, properties, SamlProviderConfig.class);
351+
}
352+
347353
OidcProviderConfig getOidcProviderConfig(String providerId) throws FirebaseAuthException {
348354
GenericUrl url = new GenericUrl(idpConfigMgtBaseUrl + getOidcUrlSuffix(providerId));
349355
return sendRequest("GET", url, null, OidcProviderConfig.class);
@@ -384,12 +390,19 @@ void deleteSamlProviderConfig(String providerId) throws FirebaseAuthException {
384390
sendRequest("DELETE", url, null, GenericJson.class);
385391
}
386392

387-
private static String generateMask(Map<String, Object> properties) {
388-
// This implementation does not currently handle the case of nested properties. This is fine
389-
// since we do not currently generate masks for any properties with nested values. When it
390-
// comes time to implement this, we can check if a property has nested properties by checking
391-
// if it is an instance of the Map class.
392-
return Joiner.on(",").join(ImmutableSortedSet.copyOf(properties.keySet()));
393+
private static Set<String> generateMask(Map<String, Object> properties) {
394+
ImmutableSortedSet.Builder<String> maskBuilder = ImmutableSortedSet.naturalOrder();
395+
for (Map.Entry<String, Object> entry : properties.entrySet()) {
396+
if (entry.getValue() instanceof Map) {
397+
Set<String> childMask = generateMask((Map<String, Object>) entry.getValue());
398+
for (String childProperty : childMask) {
399+
maskBuilder.add(entry.getKey() + "." + childProperty);
400+
}
401+
} else {
402+
maskBuilder.add(entry.getKey());
403+
}
404+
}
405+
return maskBuilder.build();
393406
}
394407

395408
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
@@ -708,7 +708,17 @@ public void testSamlProviderConfigLifecycle() throws Exception {
708708
assertEquals("RP_ENTITY_ID", config.getRpEntityId());
709709
assertEquals("https://projectId.firebaseapp.com/__/auth/handler", config.getCallbackUrl());
710710

711-
// TODO(micahstairs): Once implemented, add tests for updating the SAML provider config.
711+
// Update provider config
712+
SamlProviderConfig.UpdateRequest updateRequest =
713+
new SamlProviderConfig.UpdateRequest(providerId)
714+
.setDisplayName("NewDisplayName")
715+
.setEnabled(false)
716+
.addX509Certificate("certificate");
717+
config = auth.updateSamlProviderConfigAsync(updateRequest).get();
718+
assertEquals(providerId, config.getProviderId());
719+
assertEquals("NewDisplayName", config.getDisplayName());
720+
assertFalse(config.isEnabled());
721+
assertEquals(ImmutableList.of("certificate"), config.getX509Certificates());
712722

713723
// Delete provider config
714724
temporaryProviderConfig.deleteSamlProviderConfig(providerId);

0 commit comments

Comments
 (0)