Skip to content

Commit 0c76cc6

Browse files
authored
Add operations to create and delete OIDC provider configs. (#400)
This adds an operation to create OIDC provider configs, as well as an operation to delete provider configs. These operations can be performed using either the tenant-aware or standard Firebase client. This work is part of adding multi-tenancy support (see issue #332).
1 parent 9f5d0f0 commit 0c76cc6

File tree

5 files changed

+382
-42
lines changed

5 files changed

+382
-42
lines changed

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

Lines changed: 113 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@
3131
import com.google.firebase.auth.FirebaseUserManager.UserImportRequest;
3232
import com.google.firebase.auth.ListUsersPage.DefaultUserSource;
3333
import com.google.firebase.auth.ListUsersPage.PageFactory;
34-
import com.google.firebase.auth.UserRecord.CreateRequest;
35-
import com.google.firebase.auth.UserRecord.UpdateRequest;
34+
import com.google.firebase.auth.UserRecord;
3635
import com.google.firebase.auth.internal.FirebaseTokenFactory;
3736
import com.google.firebase.internal.CallableOperation;
3837
import com.google.firebase.internal.NonNull;
@@ -320,7 +319,8 @@ private CallableOperation<Void, FirebaseAuthException> revokeRefreshTokensOp(fin
320319
@Override
321320
protected Void execute() throws FirebaseAuthException {
322321
int currentTimeSeconds = (int) (System.currentTimeMillis() / 1000);
323-
UpdateRequest request = new UpdateRequest(uid).setValidSince(currentTimeSeconds);
322+
UserRecord.UpdateRequest request =
323+
new UserRecord.UpdateRequest(uid).setValidSince(currentTimeSeconds);
324324
userManager.updateUser(request, jsonFactory);
325325
return null;
326326
}
@@ -512,32 +512,33 @@ protected ListUsersPage execute() throws FirebaseAuthException {
512512

513513
/**
514514
* Creates a new user account with the attributes contained in the specified {@link
515-
* CreateRequest}.
515+
* UserRecord.CreateRequest}.
516516
*
517-
* @param request A non-null {@link CreateRequest} instance.
517+
* @param request A non-null {@link UserRecord.CreateRequest} instance.
518518
* @return A {@link UserRecord} instance corresponding to the newly created account.
519519
* @throws NullPointerException if the provided request is null.
520520
* @throws FirebaseAuthException if an error occurs while creating the user account.
521521
*/
522-
public UserRecord createUser(@NonNull CreateRequest request) throws FirebaseAuthException {
522+
public UserRecord createUser(@NonNull UserRecord.CreateRequest request)
523+
throws FirebaseAuthException {
523524
return createUserOp(request).call();
524525
}
525526

526527
/**
527-
* Similar to {@link #createUser(CreateRequest)} but performs the operation asynchronously.
528+
* Similar to {@link #createUser} but performs the operation asynchronously.
528529
*
529-
* @param request A non-null {@link CreateRequest} instance.
530+
* @param request A non-null {@link UserRecord.CreateRequest} instance.
530531
* @return An {@code ApiFuture} which will complete successfully with a {@link UserRecord}
531532
* instance corresponding to the newly created account. If an error occurs while creating the
532533
* user account, the future throws a {@link FirebaseAuthException}.
533534
* @throws NullPointerException if the provided request is null.
534535
*/
535-
public ApiFuture<UserRecord> createUserAsync(@NonNull CreateRequest request) {
536+
public ApiFuture<UserRecord> createUserAsync(@NonNull UserRecord.CreateRequest request) {
536537
return createUserOp(request).callAsync(firebaseApp);
537538
}
538539

539540
private CallableOperation<UserRecord, FirebaseAuthException> createUserOp(
540-
final CreateRequest request) {
541+
final UserRecord.CreateRequest request) {
541542
checkNotDestroyed();
542543
checkNotNull(request, "create request must not be null");
543544
final FirebaseUserManager userManager = getUserManager();
@@ -552,31 +553,32 @@ protected UserRecord execute() throws FirebaseAuthException {
552553

553554
/**
554555
* Updates an existing user account with the attributes contained in the specified {@link
555-
* UpdateRequest}.
556+
* UserRecord.UpdateRequest}.
556557
*
557-
* @param request A non-null {@link UpdateRequest} instance.
558+
* @param request A non-null {@link UserRecord.UpdateRequest} instance.
558559
* @return A {@link UserRecord} instance corresponding to the updated user account.
559560
* @throws NullPointerException if the provided update request is null.
560561
* @throws FirebaseAuthException if an error occurs while updating the user account.
561562
*/
562-
public UserRecord updateUser(@NonNull UpdateRequest request) throws FirebaseAuthException {
563+
public UserRecord updateUser(@NonNull UserRecord.UpdateRequest request)
564+
throws FirebaseAuthException {
563565
return updateUserOp(request).call();
564566
}
565567

566568
/**
567-
* Similar to {@link #updateUser(UpdateRequest)} but performs the operation asynchronously.
569+
* Similar to {@link #updateUser} but performs the operation asynchronously.
568570
*
569-
* @param request A non-null {@link UpdateRequest} instance.
571+
* @param request A non-null {@link UserRecord.UpdateRequest} instance.
570572
* @return An {@code ApiFuture} which will complete successfully with a {@link UserRecord}
571573
* instance corresponding to the updated user account. If an error occurs while updating the
572574
* user account, the future throws a {@link FirebaseAuthException}.
573575
*/
574-
public ApiFuture<UserRecord> updateUserAsync(@NonNull UpdateRequest request) {
576+
public ApiFuture<UserRecord> updateUserAsync(@NonNull UserRecord.UpdateRequest request) {
575577
return updateUserOp(request).callAsync(firebaseApp);
576578
}
577579

578580
private CallableOperation<UserRecord, FirebaseAuthException> updateUserOp(
579-
final UpdateRequest request) {
581+
final UserRecord.UpdateRequest request) {
580582
checkNotDestroyed();
581583
checkNotNull(request, "update request must not be null");
582584
final FirebaseUserManager userManager = getUserManager();
@@ -636,7 +638,8 @@ private CallableOperation<Void, FirebaseAuthException> setCustomUserClaimsOp(
636638
return new CallableOperation<Void, FirebaseAuthException>() {
637639
@Override
638640
protected Void execute() throws FirebaseAuthException {
639-
final UpdateRequest request = new UpdateRequest(uid).setCustomClaims(claims);
641+
final UserRecord.UpdateRequest request =
642+
new UserRecord.UpdateRequest(uid).setCustomClaims(claims);
640643
userManager.updateUser(request, jsonFactory);
641644
return null;
642645
}
@@ -917,18 +920,6 @@ public ApiFuture<String> generateSignInWithEmailLinkAsync(
917920
.callAsync(firebaseApp);
918921
}
919922

920-
FirebaseApp getFirebaseApp() {
921-
return this.firebaseApp;
922-
}
923-
924-
FirebaseTokenVerifier getCookieVerifier() {
925-
return this.cookieVerifier.get();
926-
}
927-
928-
FirebaseUserManager getUserManager() {
929-
return this.userManager.get();
930-
}
931-
932923
private CallableOperation<String, FirebaseAuthException> generateEmailActionLinkOp(
933924
final EmailLinkType type, final String email, final ActionCodeSettings settings) {
934925
checkNotDestroyed();
@@ -945,6 +936,98 @@ protected String execute() throws FirebaseAuthException {
945936
};
946937
}
947938

939+
/**
940+
* Creates a new provider OIDC Auth config with the attributes contained in the specified {@link
941+
* OidcProviderConfig.CreateRequest}.
942+
*
943+
* @param request A non-null {@link OidcProviderConfig.CreateRequest} instance.
944+
* @return An {@link OidcProviderConfig} instance corresponding to the newly created provider
945+
* config.
946+
* @throws NullPointerException if the provided request is null.
947+
* @throws FirebaseAuthException if an error occurs while creating the provider config.
948+
*/
949+
public OidcProviderConfig createOidcProviderConfig(
950+
@NonNull OidcProviderConfig.CreateRequest request) throws FirebaseAuthException {
951+
return createOidcProviderConfigOp(request).call();
952+
}
953+
954+
/**
955+
* Similar to {@link #createOidcProviderConfig} but performs the operation asynchronously.
956+
*
957+
* @param request A non-null {@link OidcProviderConfig.CreateRequest} instance.
958+
* @return An {@code ApiFuture} which will complete successfully with a {@link OidcProviderConfig}
959+
* instance corresponding to the newly created provider config. If an error occurs while
960+
* creating the provider config, the future throws a {@link FirebaseAuthException}.
961+
* @throws NullPointerException if the provided request is null.
962+
*/
963+
public ApiFuture<OidcProviderConfig> createOidcProviderConfigAsync(
964+
@NonNull OidcProviderConfig.CreateRequest request) {
965+
return createOidcProviderConfigOp(request).callAsync(firebaseApp);
966+
}
967+
968+
private CallableOperation<OidcProviderConfig, FirebaseAuthException>
969+
createOidcProviderConfigOp(final OidcProviderConfig.CreateRequest request) {
970+
checkNotDestroyed();
971+
checkNotNull(request, "create request must not be null");
972+
final FirebaseUserManager userManager = getUserManager();
973+
return new CallableOperation<OidcProviderConfig, FirebaseAuthException>() {
974+
@Override
975+
protected OidcProviderConfig execute() throws FirebaseAuthException {
976+
return userManager.createOidcProviderConfig(request);
977+
}
978+
};
979+
}
980+
981+
/**
982+
* Deletes the provider config identified by the specified provider ID.
983+
*
984+
* @param providerId A provider ID string.
985+
* @throws IllegalArgumentException If the provider ID string is null or empty.
986+
* @throws FirebaseAuthException If an error occurs while deleting the provider config.
987+
*/
988+
public void deleteProviderConfig(@NonNull String providerId) throws FirebaseAuthException {
989+
deleteProviderConfigOp(providerId).call();
990+
}
991+
992+
/**
993+
* Similar to {@link #deleteProviderConfig} but performs the operation asynchronously.
994+
*
995+
* @param providerId A provider ID string.
996+
* @return An {@code ApiFuture} which will complete successfully when the specified provider
997+
* config has been deleted. If an error occurs while deleting the provider config, the future
998+
* throws a {@link FirebaseAuthException}.
999+
* @throws IllegalArgumentException If the provider ID string is null or empty.
1000+
*/
1001+
public ApiFuture<Void> deleteProviderConfigAsync(String providerId) {
1002+
return deleteProviderConfigOp(providerId).callAsync(firebaseApp);
1003+
}
1004+
1005+
private CallableOperation<Void, FirebaseAuthException> deleteProviderConfigOp(
1006+
final String providerId) {
1007+
checkNotDestroyed();
1008+
checkArgument(!Strings.isNullOrEmpty(providerId), "provider ID must not be null or empty");
1009+
final FirebaseUserManager userManager = getUserManager();
1010+
return new CallableOperation<Void, FirebaseAuthException>() {
1011+
@Override
1012+
protected Void execute() throws FirebaseAuthException {
1013+
userManager.deleteProviderConfig(providerId);
1014+
return null;
1015+
}
1016+
};
1017+
}
1018+
1019+
FirebaseApp getFirebaseApp() {
1020+
return this.firebaseApp;
1021+
}
1022+
1023+
FirebaseTokenVerifier getCookieVerifier() {
1024+
return this.cookieVerifier.get();
1025+
}
1026+
1027+
FirebaseUserManager getUserManager() {
1028+
return this.userManager.get();
1029+
}
1030+
9481031
protected <T> Supplier<T> threadSafeMemoize(final Supplier<T> supplier) {
9491032
return Suppliers.memoize(
9501033
new Supplier<T>() {

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

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
*/
6666
class FirebaseUserManager {
6767

68+
static final String CONFIGURATION_NOT_FOUND = "configuration-not-found";
6869
static final String TENANT_ID_MISMATCH_ERROR = "tenant-id-mismatch";
6970
static final String TENANT_NOT_FOUND_ERROR = "tenant-not-found";
7071
static final String USER_NOT_FOUND_ERROR = "user-not-found";
@@ -74,7 +75,7 @@ class FirebaseUserManager {
7475
// SDK error codes defined at: https://firebase.google.com/docs/auth/admin/errors
7576
private static final Map<String, String> ERROR_CODES = ImmutableMap.<String, String>builder()
7677
.put("CLAIMS_TOO_LARGE", "claims-too-large")
77-
.put("CONFIGURATION_NOT_FOUND", "project-not-found")
78+
.put("CONFIGURATION_NOT_FOUND", CONFIGURATION_NOT_FOUND)
7879
.put("INSUFFICIENT_PERMISSION", "insufficient-permission")
7980
.put("DUPLICATE_EMAIL", "email-already-exists")
8081
.put("DUPLICATE_LOCAL_ID", "uid-already-exists")
@@ -106,6 +107,7 @@ class FirebaseUserManager {
106107
private static final String CLIENT_VERSION_HEADER = "X-Client-Version";
107108

108109
private final String userMgtBaseUrl;
110+
private final String idpConfigMgtBaseUrl;
109111
private final String tenantMgtBaseUrl;
110112
private final JsonFactory jsonFactory;
111113
private final HttpRequestFactory requestFactory;
@@ -120,15 +122,18 @@ class FirebaseUserManager {
120122
"Project ID is required to access the auth service. Use a service account credential or "
121123
+ "set the project ID explicitly via FirebaseOptions. Alternatively you can also "
122124
+ "set the project ID via the GOOGLE_CLOUD_PROJECT environment variable.");
123-
String tenantId = builder.tenantId;
124-
if (builder.tenantId == null) {
125-
this.userMgtBaseUrl = String.format(ID_TOOLKIT_URL, "v1", projectId);
125+
final String idToolkitUrlV1 = String.format(ID_TOOLKIT_URL, "v1", projectId);
126+
final String idToolkitUrlV2 = String.format(ID_TOOLKIT_URL, "v2", projectId);
127+
final String tenantId = builder.tenantId;
128+
if (tenantId == null) {
129+
this.userMgtBaseUrl = idToolkitUrlV1;
130+
this.idpConfigMgtBaseUrl = idToolkitUrlV2;
126131
} else {
127132
checkArgument(!tenantId.isEmpty(), "tenant ID must not be empty");
128-
this.userMgtBaseUrl =
129-
String.format(ID_TOOLKIT_URL, "v1", projectId) + getTenantUrlSuffix(tenantId);
133+
this.userMgtBaseUrl = idToolkitUrlV1 + getTenantUrlSuffix(tenantId);
134+
this.idpConfigMgtBaseUrl = idToolkitUrlV2 + getTenantUrlSuffix(tenantId);
130135
}
131-
this.tenantMgtBaseUrl = String.format(ID_TOOLKIT_URL, "v2", projectId);
136+
this.tenantMgtBaseUrl = idToolkitUrlV2;
132137
this.jsonFactory = app.getOptions().getJsonFactory();
133138
this.requestFactory = builder.requestFactory == null
134139
? ApiClientUtils.newAuthorizedRequestFactory(app) : builder.requestFactory;
@@ -316,6 +321,20 @@ String getEmailActionLink(EmailLinkType type, String email,
316321
throw new FirebaseAuthException(INTERNAL_ERROR, "Failed to create email action link");
317322
}
318323

324+
OidcProviderConfig createOidcProviderConfig(
325+
OidcProviderConfig.CreateRequest request) throws FirebaseAuthException {
326+
GenericUrl url = new GenericUrl(idpConfigMgtBaseUrl + "/oauthIdpConfigs");
327+
String providerId = request.getProviderId();
328+
checkArgument(!Strings.isNullOrEmpty(providerId), "provider ID must not be null or empty");
329+
url.set("oauthIdpConfigId", providerId);
330+
return sendRequest("POST", url, request.getProperties(), OidcProviderConfig.class);
331+
}
332+
333+
void deleteProviderConfig(String providerId) throws FirebaseAuthException {
334+
GenericUrl url = new GenericUrl(idpConfigMgtBaseUrl + "/oauthIdpConfigs/" + providerId);
335+
sendRequest("DELETE", url, null, GenericJson.class);
336+
}
337+
319338
private static String getTenantUrlSuffix(String tenantId) {
320339
checkArgument(!Strings.isNullOrEmpty(tenantId));
321340
return "/tenants/" + tenantId;

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

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,69 @@ public void testGenerateSignInWithEmailLink() throws Exception {
959959
}
960960
}
961961

962+
@Test
963+
public void testOidcProviderConfigLifecycle() throws Exception {
964+
// Create config provider
965+
String providerId = "oidc.provider-id";
966+
OidcProviderConfig.CreateRequest createRequest =
967+
new OidcProviderConfig.CreateRequest()
968+
.setProviderId(providerId)
969+
.setDisplayName("DisplayName")
970+
.setEnabled(true)
971+
.setClientId("ClientId")
972+
.setIssuer("https://oidc.com/issuer");
973+
OidcProviderConfig config = auth.createOidcProviderConfigAsync(createRequest).get();
974+
assertEquals(providerId, config.getProviderId());
975+
assertEquals("DisplayName", config.getDisplayName());
976+
assertEquals("ClientId", config.getClientId());
977+
assertEquals("https://oidc.com/issuer", config.getIssuer());
978+
979+
// TODO(micahstairs): Test getOidcProviderConfig and updateProviderConfig operations.
980+
981+
// Delete config provider
982+
auth.deleteProviderConfigAsync(providerId).get();
983+
// TODO(micahstairs): Once getOidcProviderConfig operation is implemented, add a check here to
984+
// double-check that the config provider was deleted.
985+
}
986+
987+
@Test
988+
public void testTenantAwareOidcProviderConfigLifecycle() throws Exception {
989+
// Create tenant to use.
990+
TenantManager tenantManager = auth.getTenantManager();
991+
Tenant.CreateRequest tenantCreateRequest =
992+
new Tenant.CreateRequest().setDisplayName("DisplayName");
993+
String tenantId = tenantManager.createTenant(tenantCreateRequest).getTenantId();
994+
995+
try {
996+
// Create config provider
997+
TenantAwareFirebaseAuth tenantAwareAuth = auth.getTenantManager().getAuthForTenant(tenantId);
998+
String providerId = "oidc.provider-id";
999+
OidcProviderConfig.CreateRequest createRequest =
1000+
new OidcProviderConfig.CreateRequest()
1001+
.setProviderId(providerId)
1002+
.setDisplayName("DisplayName")
1003+
.setEnabled(true)
1004+
.setClientId("ClientId")
1005+
.setIssuer("https://oidc.com/issuer");
1006+
OidcProviderConfig config =
1007+
tenantAwareAuth.createOidcProviderConfigAsync(createRequest).get();
1008+
assertEquals(providerId, config.getProviderId());
1009+
assertEquals("DisplayName", config.getDisplayName());
1010+
assertEquals("ClientId", config.getClientId());
1011+
assertEquals("https://oidc.com/issuer", config.getIssuer());
1012+
1013+
// TODO(micahstairs): Test getOidcProviderConfig and updateProviderConfig operations.
1014+
1015+
// Delete config provider
1016+
tenantAwareAuth.deleteProviderConfigAsync(providerId).get();
1017+
// TODO(micahstairs): Once getOidcProviderConfig operation is implemented, add a check here to
1018+
// double-check that the config provider was deleted.
1019+
} finally {
1020+
// Delete tenant.
1021+
tenantManager.deleteTenantAsync(tenantId).get();
1022+
}
1023+
}
1024+
9621025
private Map<String, String> parseLinkParameters(String link) throws Exception {
9631026
Map<String, String> result = new HashMap<>();
9641027
int queryBegin = link.indexOf('?');

0 commit comments

Comments
 (0)