Skip to content

Commit db81c06

Browse files
committed
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 a2fa782 commit db81c06

File tree

5 files changed

+383
-43
lines changed

5 files changed

+383
-43
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;
@@ -323,7 +322,8 @@ private CallableOperation<Void, FirebaseAuthException> revokeRefreshTokensOp(fin
323322
@Override
324323
protected Void execute() throws FirebaseAuthException {
325324
int currentTimeSeconds = (int) (System.currentTimeMillis() / 1000);
326-
UpdateRequest request = new UpdateRequest(uid).setValidSince(currentTimeSeconds);
325+
UserRecord.UpdateRequest request =
326+
new UserRecord.UpdateRequest(uid).setValidSince(currentTimeSeconds);
327327
userManager.updateUser(request, jsonFactory);
328328
return null;
329329
}
@@ -515,32 +515,33 @@ protected ListUsersPage execute() throws FirebaseAuthException {
515515

516516
/**
517517
* Creates a new user account with the attributes contained in the specified {@link
518-
* CreateRequest}.
518+
* UserRecord.CreateRequest}.
519519
*
520-
* @param request A non-null {@link CreateRequest} instance.
520+
* @param request A non-null {@link UserRecord.CreateRequest} instance.
521521
* @return A {@link UserRecord} instance corresponding to the newly created account.
522522
* @throws NullPointerException if the provided request is null.
523523
* @throws FirebaseAuthException if an error occurs while creating the user account.
524524
*/
525-
public UserRecord createUser(@NonNull CreateRequest request) throws FirebaseAuthException {
525+
public UserRecord createUser(@NonNull UserRecord.CreateRequest request)
526+
throws FirebaseAuthException {
526527
return createUserOp(request).call();
527528
}
528529

529530
/**
530-
* Similar to {@link #createUser(CreateRequest)} but performs the operation asynchronously.
531+
* Similar to {@link #createUser} but performs the operation asynchronously.
531532
*
532-
* @param request A non-null {@link CreateRequest} instance.
533+
* @param request A non-null {@link UserRecord.CreateRequest} instance.
533534
* @return An {@code ApiFuture} which will complete successfully with a {@link UserRecord}
534535
* instance corresponding to the newly created account. If an error occurs while creating the
535536
* user account, the future throws a {@link FirebaseAuthException}.
536537
* @throws NullPointerException if the provided request is null.
537538
*/
538-
public ApiFuture<UserRecord> createUserAsync(@NonNull CreateRequest request) {
539+
public ApiFuture<UserRecord> createUserAsync(@NonNull UserRecord.CreateRequest request) {
539540
return createUserOp(request).callAsync(firebaseApp);
540541
}
541542

542543
private CallableOperation<UserRecord, FirebaseAuthException> createUserOp(
543-
final CreateRequest request) {
544+
final UserRecord.CreateRequest request) {
544545
checkNotDestroyed();
545546
checkNotNull(request, "create request must not be null");
546547
final FirebaseUserManager userManager = getUserManager();
@@ -555,31 +556,32 @@ protected UserRecord execute() throws FirebaseAuthException {
555556

556557
/**
557558
* Updates an existing user account with the attributes contained in the specified {@link
558-
* UpdateRequest}.
559+
* UserRecord.UpdateRequest}.
559560
*
560-
* @param request A non-null {@link UpdateRequest} instance.
561+
* @param request A non-null {@link UserRecord.UpdateRequest} instance.
561562
* @return A {@link UserRecord} instance corresponding to the updated user account.
562563
* @throws NullPointerException if the provided update request is null.
563564
* @throws FirebaseAuthException if an error occurs while updating the user account.
564565
*/
565-
public UserRecord updateUser(@NonNull UpdateRequest request) throws FirebaseAuthException {
566+
public UserRecord updateUser(@NonNull UserRecord.UpdateRequest request)
567+
throws FirebaseAuthException {
566568
return updateUserOp(request).call();
567569
}
568570

569571
/**
570-
* Similar to {@link #updateUser(UpdateRequest)} but performs the operation asynchronously.
572+
* Similar to {@link #updateUser} but performs the operation asynchronously.
571573
*
572-
* @param request A non-null {@link UpdateRequest} instance.
574+
* @param request A non-null {@link UserRecord.UpdateRequest} instance.
573575
* @return An {@code ApiFuture} which will complete successfully with a {@link UserRecord}
574576
* instance corresponding to the updated user account. If an error occurs while updating the
575577
* user account, the future throws a {@link FirebaseAuthException}.
576578
*/
577-
public ApiFuture<UserRecord> updateUserAsync(@NonNull UpdateRequest request) {
579+
public ApiFuture<UserRecord> updateUserAsync(@NonNull UserRecord.UpdateRequest request) {
578580
return updateUserOp(request).callAsync(firebaseApp);
579581
}
580582

581583
private CallableOperation<UserRecord, FirebaseAuthException> updateUserOp(
582-
final UpdateRequest request) {
584+
final UserRecord.UpdateRequest request) {
583585
checkNotDestroyed();
584586
checkNotNull(request, "update request must not be null");
585587
final FirebaseUserManager userManager = getUserManager();
@@ -639,7 +641,8 @@ private CallableOperation<Void, FirebaseAuthException> setCustomUserClaimsOp(
639641
return new CallableOperation<Void, FirebaseAuthException>() {
640642
@Override
641643
protected Void execute() throws FirebaseAuthException {
642-
final UpdateRequest request = new UpdateRequest(uid).setCustomClaims(claims);
644+
final UserRecord.UpdateRequest request =
645+
new UserRecord.UpdateRequest(uid).setCustomClaims(claims);
643646
userManager.updateUser(request, jsonFactory);
644647
return null;
645648
}
@@ -1054,18 +1057,6 @@ public ApiFuture<String> generateSignInWithEmailLinkAsync(
10541057
.callAsync(firebaseApp);
10551058
}
10561059

1057-
FirebaseApp getFirebaseApp() {
1058-
return this.firebaseApp;
1059-
}
1060-
1061-
FirebaseTokenVerifier getCookieVerifier() {
1062-
return this.cookieVerifier.get();
1063-
}
1064-
1065-
FirebaseUserManager getUserManager() {
1066-
return this.userManager.get();
1067-
}
1068-
10691060
private CallableOperation<String, FirebaseAuthException> generateEmailActionLinkOp(
10701061
final EmailLinkType type, final String email, final ActionCodeSettings settings) {
10711062
checkNotDestroyed();
@@ -1082,6 +1073,98 @@ protected String execute() throws FirebaseAuthException {
10821073
};
10831074
}
10841075

1076+
/**
1077+
* Creates a new provider OIDC Auth config with the attributes contained in the specified {@link
1078+
* OidcProviderConfig.CreateRequest}.
1079+
*
1080+
* @param request A non-null {@link OidcProviderConfig.CreateRequest} instance.
1081+
* @return An {@link OidcProviderConfig} instance corresponding to the newly created provider
1082+
* config.
1083+
* @throws NullPointerException if the provided request is null.
1084+
* @throws FirebaseAuthException if an error occurs while creating the provider config.
1085+
*/
1086+
public OidcProviderConfig createOidcProviderConfig(
1087+
@NonNull OidcProviderConfig.CreateRequest request) throws FirebaseAuthException {
1088+
return createOidcProviderConfigOp(request).call();
1089+
}
1090+
1091+
/**
1092+
* Similar to {@link #createOidcProviderConfig} but performs the operation asynchronously.
1093+
*
1094+
* @param request A non-null {@link OidcProviderConfig.CreateRequest} instance.
1095+
* @return An {@code ApiFuture} which will complete successfully with a {@link OidcProviderConfig}
1096+
* instance corresponding to the newly created provider config. If an error occurs while
1097+
* creating the provider config, the future throws a {@link FirebaseAuthException}.
1098+
* @throws NullPointerException if the provided request is null.
1099+
*/
1100+
public ApiFuture<OidcProviderConfig> createOidcProviderConfigAsync(
1101+
@NonNull OidcProviderConfig.CreateRequest request) {
1102+
return createOidcProviderConfigOp(request).callAsync(firebaseApp);
1103+
}
1104+
1105+
private CallableOperation<OidcProviderConfig, FirebaseAuthException>
1106+
createOidcProviderConfigOp(final OidcProviderConfig.CreateRequest request) {
1107+
checkNotDestroyed();
1108+
checkNotNull(request, "create request must not be null");
1109+
final FirebaseUserManager userManager = getUserManager();
1110+
return new CallableOperation<OidcProviderConfig, FirebaseAuthException>() {
1111+
@Override
1112+
protected OidcProviderConfig execute() throws FirebaseAuthException {
1113+
return userManager.createOidcProviderConfig(request);
1114+
}
1115+
};
1116+
}
1117+
1118+
/**
1119+
* Deletes the provider config identified by the specified provider ID.
1120+
*
1121+
* @param providerId A provider ID string.
1122+
* @throws IllegalArgumentException If the provider ID string is null or empty.
1123+
* @throws FirebaseAuthException If an error occurs while deleting the provider config.
1124+
*/
1125+
public void deleteProviderConfig(@NonNull String providerId) throws FirebaseAuthException {
1126+
deleteProviderConfigOp(providerId).call();
1127+
}
1128+
1129+
/**
1130+
* Similar to {@link #deleteProviderConfig} but performs the operation asynchronously.
1131+
*
1132+
* @param providerId A provider ID string.
1133+
* @return An {@code ApiFuture} which will complete successfully when the specified provider
1134+
* config has been deleted. If an error occurs while deleting the provider config, the future
1135+
* throws a {@link FirebaseAuthException}.
1136+
* @throws IllegalArgumentException If the provider ID string is null or empty.
1137+
*/
1138+
public ApiFuture<Void> deleteProviderConfigAsync(String providerId) {
1139+
return deleteProviderConfigOp(providerId).callAsync(firebaseApp);
1140+
}
1141+
1142+
private CallableOperation<Void, FirebaseAuthException> deleteProviderConfigOp(
1143+
final String providerId) {
1144+
checkNotDestroyed();
1145+
checkArgument(!Strings.isNullOrEmpty(providerId), "provider ID must not be null or empty");
1146+
final FirebaseUserManager userManager = getUserManager();
1147+
return new CallableOperation<Void, FirebaseAuthException>() {
1148+
@Override
1149+
protected Void execute() throws FirebaseAuthException {
1150+
userManager.deleteProviderConfig(providerId);
1151+
return null;
1152+
}
1153+
};
1154+
}
1155+
1156+
FirebaseApp getFirebaseApp() {
1157+
return this.firebaseApp;
1158+
}
1159+
1160+
FirebaseTokenVerifier getCookieVerifier() {
1161+
return this.cookieVerifier.get();
1162+
}
1163+
1164+
FirebaseUserManager getUserManager() {
1165+
return this.userManager.get();
1166+
}
1167+
10851168
protected <T> Supplier<T> threadSafeMemoize(final Supplier<T> supplier) {
10861169
return Suppliers.memoize(
10871170
new Supplier<T>() {

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

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
*/
7070
class FirebaseUserManager {
7171

72+
static final String CONFIGURATION_NOT_FOUND = "configuration-not-found";
7273
static final String TENANT_ID_MISMATCH_ERROR = "tenant-id-mismatch";
7374
static final String TENANT_NOT_FOUND_ERROR = "tenant-not-found";
7475
static final String USER_NOT_FOUND_ERROR = "user-not-found";
@@ -78,7 +79,7 @@ class FirebaseUserManager {
7879
// SDK error codes defined at: https://firebase.google.com/docs/auth/admin/errors
7980
private static final Map<String, String> ERROR_CODES = ImmutableMap.<String, String>builder()
8081
.put("CLAIMS_TOO_LARGE", "claims-too-large")
81-
.put("CONFIGURATION_NOT_FOUND", "project-not-found")
82+
.put("CONFIGURATION_NOT_FOUND", CONFIGURATION_NOT_FOUND)
8283
.put("INSUFFICIENT_PERMISSION", "insufficient-permission")
8384
.put("DUPLICATE_EMAIL", "email-already-exists")
8485
.put("DUPLICATE_LOCAL_ID", "uid-already-exists")
@@ -112,6 +113,7 @@ class FirebaseUserManager {
112113
private static final String CLIENT_VERSION_HEADER = "X-Client-Version";
113114

114115
private final String userMgtBaseUrl;
116+
private final String idpConfigMgtBaseUrl;
115117
private final String tenantMgtBaseUrl;
116118
private final JsonFactory jsonFactory;
117119
private final HttpRequestFactory requestFactory;
@@ -126,15 +128,18 @@ class FirebaseUserManager {
126128
"Project ID is required to access the auth service. Use a service account credential or "
127129
+ "set the project ID explicitly via FirebaseOptions. Alternatively you can also "
128130
+ "set the project ID via the GOOGLE_CLOUD_PROJECT environment variable.");
129-
String tenantId = builder.tenantId;
130-
if (builder.tenantId == null) {
131-
this.userMgtBaseUrl = String.format(ID_TOOLKIT_URL, "v1", projectId);
131+
final String idToolkitUrlV1 = String.format(ID_TOOLKIT_URL, "v1", projectId);
132+
final String idToolkitUrlV2 = String.format(ID_TOOLKIT_URL, "v2", projectId);
133+
final String tenantId = builder.tenantId;
134+
if (tenantId == null) {
135+
this.userMgtBaseUrl = idToolkitUrlV1;
136+
this.idpConfigMgtBaseUrl = idToolkitUrlV2;
132137
} else {
133-
checkArgument(!tenantId.isEmpty(), "Tenant ID must not be empty");
134-
this.userMgtBaseUrl =
135-
String.format(ID_TOOLKIT_URL, "v1", projectId) + getTenantUrlSuffix(tenantId);
138+
checkArgument(!tenantId.isEmpty(), "Tenant ID must not be empty.");
139+
this.userMgtBaseUrl = idToolkitUrlV1 + getTenantUrlSuffix(tenantId);
140+
this.idpConfigMgtBaseUrl = idToolkitUrlV2 + getTenantUrlSuffix(tenantId);
136141
}
137-
this.tenantMgtBaseUrl = String.format(ID_TOOLKIT_URL, "v2", projectId);
142+
this.tenantMgtBaseUrl = idToolkitUrlV2;
138143
this.jsonFactory = app.getOptions().getJsonFactory();
139144
this.requestFactory = builder.requestFactory == null
140145
? ApiClientUtils.newAuthorizedRequestFactory(app) : builder.requestFactory;
@@ -366,6 +371,20 @@ String getEmailActionLink(EmailLinkType type, String email,
366371
throw new FirebaseAuthException(INTERNAL_ERROR, "Failed to create email action link");
367372
}
368373

374+
OidcProviderConfig createOidcProviderConfig(
375+
OidcProviderConfig.CreateRequest request) throws FirebaseAuthException {
376+
GenericUrl url = new GenericUrl(idpConfigMgtBaseUrl + "/oauthIdpConfigs");
377+
String providerId = request.getProviderId();
378+
checkArgument(!Strings.isNullOrEmpty(providerId), "provider ID must not be null or empty");
379+
url.set("oauthIdpConfigId", providerId);
380+
return sendRequest("POST", url, request.getProperties(), OidcProviderConfig.class);
381+
}
382+
383+
void deleteProviderConfig(String providerId) throws FirebaseAuthException {
384+
GenericUrl url = new GenericUrl(idpConfigMgtBaseUrl + "/oauthIdpConfigs/" + providerId);
385+
sendRequest("DELETE", url, null, GenericJson.class);
386+
}
387+
369388
private static String getTenantUrlSuffix(String tenantId) {
370389
checkArgument(!Strings.isNullOrEmpty(tenantId));
371390
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
@@ -1060,6 +1060,69 @@ public void testGenerateSignInWithEmailLink() throws Exception {
10601060
}
10611061
}
10621062

1063+
@Test
1064+
public void testOidcProviderConfigLifecycle() throws Exception {
1065+
// Create config provider
1066+
String providerId = "oidc.provider-id";
1067+
OidcProviderConfig.CreateRequest createRequest =
1068+
new OidcProviderConfig.CreateRequest()
1069+
.setProviderId(providerId)
1070+
.setDisplayName("DisplayName")
1071+
.setEnabled(true)
1072+
.setClientId("ClientId")
1073+
.setIssuer("https://oidc.com/issuer");
1074+
OidcProviderConfig config = auth.createOidcProviderConfigAsync(createRequest).get();
1075+
assertEquals(providerId, config.getProviderId());
1076+
assertEquals("DisplayName", config.getDisplayName());
1077+
assertEquals("ClientId", config.getClientId());
1078+
assertEquals("https://oidc.com/issuer", config.getIssuer());
1079+
1080+
// TODO(micahstairs): Test getOidcProviderConfig and updateProviderConfig operations.
1081+
1082+
// Delete config provider
1083+
auth.deleteProviderConfigAsync(providerId).get();
1084+
// TODO(micahstairs): Once getOidcProviderConfig operation is implemented, add a check here to
1085+
// double-check that the config provider was deleted.
1086+
}
1087+
1088+
@Test
1089+
public void testTenantAwareOidcProviderConfigLifecycle() throws Exception {
1090+
// Create tenant to use.
1091+
TenantManager tenantManager = auth.getTenantManager();
1092+
Tenant.CreateRequest tenantCreateRequest =
1093+
new Tenant.CreateRequest().setDisplayName("DisplayName");
1094+
String tenantId = tenantManager.createTenant(tenantCreateRequest).getTenantId();
1095+
1096+
try {
1097+
// Create config provider
1098+
TenantAwareFirebaseAuth tenantAwareAuth = auth.getTenantManager().getAuthForTenant(tenantId);
1099+
String providerId = "oidc.provider-id";
1100+
OidcProviderConfig.CreateRequest createRequest =
1101+
new OidcProviderConfig.CreateRequest()
1102+
.setProviderId(providerId)
1103+
.setDisplayName("DisplayName")
1104+
.setEnabled(true)
1105+
.setClientId("ClientId")
1106+
.setIssuer("https://oidc.com/issuer");
1107+
OidcProviderConfig config =
1108+
tenantAwareAuth.createOidcProviderConfigAsync(createRequest).get();
1109+
assertEquals(providerId, config.getProviderId());
1110+
assertEquals("DisplayName", config.getDisplayName());
1111+
assertEquals("ClientId", config.getClientId());
1112+
assertEquals("https://oidc.com/issuer", config.getIssuer());
1113+
1114+
// TODO(micahstairs): Test getOidcProviderConfig and updateProviderConfig operations.
1115+
1116+
// Delete config provider
1117+
tenantAwareAuth.deleteProviderConfigAsync(providerId).get();
1118+
// TODO(micahstairs): Once getOidcProviderConfig operation is implemented, add a check here to
1119+
// double-check that the config provider was deleted.
1120+
} finally {
1121+
// Delete tenant.
1122+
tenantManager.deleteTenantAsync(tenantId).get();
1123+
}
1124+
}
1125+
10631126
private Map<String, String> parseLinkParameters(String link) throws Exception {
10641127
Map<String, String> result = new HashMap<>();
10651128
int queryBegin = link.indexOf('?');

0 commit comments

Comments
 (0)