Skip to content

Add operations to create and delete OIDC provider configs. #400

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
143 changes: 113 additions & 30 deletions src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@
import com.google.firebase.auth.FirebaseUserManager.UserImportRequest;
import com.google.firebase.auth.ListUsersPage.DefaultUserSource;
import com.google.firebase.auth.ListUsersPage.PageFactory;
import com.google.firebase.auth.UserRecord.CreateRequest;
import com.google.firebase.auth.UserRecord.UpdateRequest;
import com.google.firebase.auth.UserRecord;
import com.google.firebase.auth.internal.FirebaseTokenFactory;
import com.google.firebase.internal.CallableOperation;
import com.google.firebase.internal.NonNull;
Expand Down Expand Up @@ -320,7 +319,8 @@ private CallableOperation<Void, FirebaseAuthException> revokeRefreshTokensOp(fin
@Override
protected Void execute() throws FirebaseAuthException {
int currentTimeSeconds = (int) (System.currentTimeMillis() / 1000);
UpdateRequest request = new UpdateRequest(uid).setValidSince(currentTimeSeconds);
UserRecord.UpdateRequest request =
new UserRecord.UpdateRequest(uid).setValidSince(currentTimeSeconds);
userManager.updateUser(request, jsonFactory);
return null;
}
Expand Down Expand Up @@ -512,32 +512,33 @@ protected ListUsersPage execute() throws FirebaseAuthException {

/**
* Creates a new user account with the attributes contained in the specified {@link
* CreateRequest}.
* UserRecord.CreateRequest}.
*
* @param request A non-null {@link CreateRequest} instance.
* @param request A non-null {@link UserRecord.CreateRequest} instance.
* @return A {@link UserRecord} instance corresponding to the newly created account.
* @throws NullPointerException if the provided request is null.
* @throws FirebaseAuthException if an error occurs while creating the user account.
*/
public UserRecord createUser(@NonNull CreateRequest request) throws FirebaseAuthException {
public UserRecord createUser(@NonNull UserRecord.CreateRequest request)
throws FirebaseAuthException {
return createUserOp(request).call();
}

/**
* Similar to {@link #createUser(CreateRequest)} but performs the operation asynchronously.
* Similar to {@link #createUser} but performs the operation asynchronously.
*
* @param request A non-null {@link CreateRequest} instance.
* @param request A non-null {@link UserRecord.CreateRequest} instance.
* @return An {@code ApiFuture} which will complete successfully with a {@link UserRecord}
* instance corresponding to the newly created account. If an error occurs while creating the
* user account, the future throws a {@link FirebaseAuthException}.
* @throws NullPointerException if the provided request is null.
*/
public ApiFuture<UserRecord> createUserAsync(@NonNull CreateRequest request) {
public ApiFuture<UserRecord> createUserAsync(@NonNull UserRecord.CreateRequest request) {
return createUserOp(request).callAsync(firebaseApp);
}

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

/**
* Updates an existing user account with the attributes contained in the specified {@link
* UpdateRequest}.
* UserRecord.UpdateRequest}.
*
* @param request A non-null {@link UpdateRequest} instance.
* @param request A non-null {@link UserRecord.UpdateRequest} instance.
* @return A {@link UserRecord} instance corresponding to the updated user account.
* @throws NullPointerException if the provided update request is null.
* @throws FirebaseAuthException if an error occurs while updating the user account.
*/
public UserRecord updateUser(@NonNull UpdateRequest request) throws FirebaseAuthException {
public UserRecord updateUser(@NonNull UserRecord.UpdateRequest request)
throws FirebaseAuthException {
return updateUserOp(request).call();
}

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

private CallableOperation<UserRecord, FirebaseAuthException> updateUserOp(
final UpdateRequest request) {
final UserRecord.UpdateRequest request) {
checkNotDestroyed();
checkNotNull(request, "update request must not be null");
final FirebaseUserManager userManager = getUserManager();
Expand Down Expand Up @@ -636,7 +638,8 @@ private CallableOperation<Void, FirebaseAuthException> setCustomUserClaimsOp(
return new CallableOperation<Void, FirebaseAuthException>() {
@Override
protected Void execute() throws FirebaseAuthException {
final UpdateRequest request = new UpdateRequest(uid).setCustomClaims(claims);
final UserRecord.UpdateRequest request =
new UserRecord.UpdateRequest(uid).setCustomClaims(claims);
userManager.updateUser(request, jsonFactory);
return null;
}
Expand Down Expand Up @@ -917,18 +920,6 @@ public ApiFuture<String> generateSignInWithEmailLinkAsync(
.callAsync(firebaseApp);
}

FirebaseApp getFirebaseApp() {
return this.firebaseApp;
}

FirebaseTokenVerifier getCookieVerifier() {
return this.cookieVerifier.get();
}

FirebaseUserManager getUserManager() {
return this.userManager.get();
}

private CallableOperation<String, FirebaseAuthException> generateEmailActionLinkOp(
final EmailLinkType type, final String email, final ActionCodeSettings settings) {
checkNotDestroyed();
Expand All @@ -945,6 +936,98 @@ protected String execute() throws FirebaseAuthException {
};
}

/**
* Creates a new provider OIDC Auth config with the attributes contained in the specified {@link
* OidcProviderConfig.CreateRequest}.
*
* @param request A non-null {@link OidcProviderConfig.CreateRequest} instance.
* @return An {@link OidcProviderConfig} instance corresponding to the newly created provider
* config.
* @throws NullPointerException if the provided request is null.
* @throws FirebaseAuthException if an error occurs while creating the provider config.
*/
public OidcProviderConfig createOidcProviderConfig(
@NonNull OidcProviderConfig.CreateRequest request) throws FirebaseAuthException {
return createOidcProviderConfigOp(request).call();
}

/**
* Similar to {@link #createOidcProviderConfig} but performs the operation asynchronously.
*
* @param request A non-null {@link OidcProviderConfig.CreateRequest} instance.
* @return An {@code ApiFuture} which will complete successfully with a {@link OidcProviderConfig}
* instance corresponding to the newly created provider config. If an error occurs while
* creating the provider config, the future throws a {@link FirebaseAuthException}.
* @throws NullPointerException if the provided request is null.
*/
public ApiFuture<OidcProviderConfig> createOidcProviderConfigAsync(
@NonNull OidcProviderConfig.CreateRequest request) {
return createOidcProviderConfigOp(request).callAsync(firebaseApp);
}

private CallableOperation<OidcProviderConfig, FirebaseAuthException>
createOidcProviderConfigOp(final OidcProviderConfig.CreateRequest request) {
checkNotDestroyed();
checkNotNull(request, "create request must not be null");
final FirebaseUserManager userManager = getUserManager();
return new CallableOperation<OidcProviderConfig, FirebaseAuthException>() {
@Override
protected OidcProviderConfig execute() throws FirebaseAuthException {
return userManager.createOidcProviderConfig(request);
}
};
}

/**
* Deletes the provider config identified by the specified provider ID.
*
* @param providerId A provider ID string.
* @throws IllegalArgumentException If the provider ID string is null or empty.
* @throws FirebaseAuthException If an error occurs while deleting the provider config.
*/
public void deleteProviderConfig(@NonNull String providerId) throws FirebaseAuthException {
deleteProviderConfigOp(providerId).call();
}

/**
* Similar to {@link #deleteProviderConfig} but performs the operation asynchronously.
*
* @param providerId A provider ID string.
* @return An {@code ApiFuture} which will complete successfully when the specified provider
* config has been deleted. If an error occurs while deleting the provider config, the future
* throws a {@link FirebaseAuthException}.
* @throws IllegalArgumentException If the provider ID string is null or empty.
*/
public ApiFuture<Void> deleteProviderConfigAsync(String providerId) {
return deleteProviderConfigOp(providerId).callAsync(firebaseApp);
}

private CallableOperation<Void, FirebaseAuthException> deleteProviderConfigOp(
final String providerId) {
checkNotDestroyed();
checkArgument(!Strings.isNullOrEmpty(providerId), "provider ID must not be null or empty");
final FirebaseUserManager userManager = getUserManager();
return new CallableOperation<Void, FirebaseAuthException>() {
@Override
protected Void execute() throws FirebaseAuthException {
userManager.deleteProviderConfig(providerId);
return null;
}
};
}

FirebaseApp getFirebaseApp() {
return this.firebaseApp;
}

FirebaseTokenVerifier getCookieVerifier() {
return this.cookieVerifier.get();
}

FirebaseUserManager getUserManager() {
return this.userManager.get();
}

protected <T> Supplier<T> threadSafeMemoize(final Supplier<T> supplier) {
return Suppliers.memoize(
new Supplier<T>() {
Expand Down
33 changes: 26 additions & 7 deletions src/main/java/com/google/firebase/auth/FirebaseUserManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
*/
class FirebaseUserManager {

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

private final String userMgtBaseUrl;
private final String idpConfigMgtBaseUrl;
private final String tenantMgtBaseUrl;
private final JsonFactory jsonFactory;
private final HttpRequestFactory requestFactory;
Expand All @@ -120,15 +122,18 @@ class FirebaseUserManager {
"Project ID is required to access the auth service. Use a service account credential or "
+ "set the project ID explicitly via FirebaseOptions. Alternatively you can also "
+ "set the project ID via the GOOGLE_CLOUD_PROJECT environment variable.");
String tenantId = builder.tenantId;
if (builder.tenantId == null) {
this.userMgtBaseUrl = String.format(ID_TOOLKIT_URL, "v1", projectId);
final String idToolkitUrlV1 = String.format(ID_TOOLKIT_URL, "v1", projectId);
final String idToolkitUrlV2 = String.format(ID_TOOLKIT_URL, "v2", projectId);
final String tenantId = builder.tenantId;
if (tenantId == null) {
this.userMgtBaseUrl = idToolkitUrlV1;
this.idpConfigMgtBaseUrl = idToolkitUrlV2;
} else {
checkArgument(!tenantId.isEmpty(), "tenant ID must not be empty");
this.userMgtBaseUrl =
String.format(ID_TOOLKIT_URL, "v1", projectId) + getTenantUrlSuffix(tenantId);
this.userMgtBaseUrl = idToolkitUrlV1 + getTenantUrlSuffix(tenantId);
this.idpConfigMgtBaseUrl = idToolkitUrlV2 + getTenantUrlSuffix(tenantId);
}
this.tenantMgtBaseUrl = String.format(ID_TOOLKIT_URL, "v2", projectId);
this.tenantMgtBaseUrl = idToolkitUrlV2;
this.jsonFactory = app.getOptions().getJsonFactory();
this.requestFactory = builder.requestFactory == null
? ApiClientUtils.newAuthorizedRequestFactory(app) : builder.requestFactory;
Expand Down Expand Up @@ -316,6 +321,20 @@ String getEmailActionLink(EmailLinkType type, String email,
throw new FirebaseAuthException(INTERNAL_ERROR, "Failed to create email action link");
}

OidcProviderConfig createOidcProviderConfig(
OidcProviderConfig.CreateRequest request) throws FirebaseAuthException {
GenericUrl url = new GenericUrl(idpConfigMgtBaseUrl + "/oauthIdpConfigs");
String providerId = request.getProviderId();
checkArgument(!Strings.isNullOrEmpty(providerId), "provider ID must not be null or empty");
url.set("oauthIdpConfigId", providerId);
return sendRequest("POST", url, request.getProperties(), OidcProviderConfig.class);
}

void deleteProviderConfig(String providerId) throws FirebaseAuthException {
GenericUrl url = new GenericUrl(idpConfigMgtBaseUrl + "/oauthIdpConfigs/" + providerId);
sendRequest("DELETE", url, null, GenericJson.class);
}

private static String getTenantUrlSuffix(String tenantId) {
checkArgument(!Strings.isNullOrEmpty(tenantId));
return "/tenants/" + tenantId;
Expand Down
63 changes: 63 additions & 0 deletions src/test/java/com/google/firebase/auth/FirebaseAuthIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -959,6 +959,69 @@ public void testGenerateSignInWithEmailLink() throws Exception {
}
}

@Test
public void testOidcProviderConfigLifecycle() throws Exception {
// Create config provider
String providerId = "oidc.provider-id";
OidcProviderConfig.CreateRequest createRequest =
new OidcProviderConfig.CreateRequest()
.setProviderId(providerId)
.setDisplayName("DisplayName")
.setEnabled(true)
.setClientId("ClientId")
.setIssuer("https://oidc.com/issuer");
OidcProviderConfig config = auth.createOidcProviderConfigAsync(createRequest).get();
assertEquals(providerId, config.getProviderId());
assertEquals("DisplayName", config.getDisplayName());
assertEquals("ClientId", config.getClientId());
assertEquals("https://oidc.com/issuer", config.getIssuer());

// TODO(micahstairs): Test getOidcProviderConfig and updateProviderConfig operations.

// Delete config provider
auth.deleteProviderConfigAsync(providerId).get();
// TODO(micahstairs): Once getOidcProviderConfig operation is implemented, add a check here to
// double-check that the config provider was deleted.
}

@Test
public void testTenantAwareOidcProviderConfigLifecycle() throws Exception {
// Create tenant to use.
TenantManager tenantManager = auth.getTenantManager();
Tenant.CreateRequest tenantCreateRequest =
new Tenant.CreateRequest().setDisplayName("DisplayName");
String tenantId = tenantManager.createTenant(tenantCreateRequest).getTenantId();

try {
// Create config provider
TenantAwareFirebaseAuth tenantAwareAuth = auth.getTenantManager().getAuthForTenant(tenantId);
String providerId = "oidc.provider-id";
OidcProviderConfig.CreateRequest createRequest =
new OidcProviderConfig.CreateRequest()
.setProviderId(providerId)
.setDisplayName("DisplayName")
.setEnabled(true)
.setClientId("ClientId")
.setIssuer("https://oidc.com/issuer");
OidcProviderConfig config =
tenantAwareAuth.createOidcProviderConfigAsync(createRequest).get();
assertEquals(providerId, config.getProviderId());
assertEquals("DisplayName", config.getDisplayName());
assertEquals("ClientId", config.getClientId());
assertEquals("https://oidc.com/issuer", config.getIssuer());

// TODO(micahstairs): Test getOidcProviderConfig and updateProviderConfig operations.

// Delete config provider
tenantAwareAuth.deleteProviderConfigAsync(providerId).get();
// TODO(micahstairs): Once getOidcProviderConfig operation is implemented, add a check here to
// double-check that the config provider was deleted.
} finally {
// Delete tenant.
tenantManager.deleteTenantAsync(tenantId).get();
}
}

private Map<String, String> parseLinkParameters(String link) throws Exception {
Map<String, String> result = new HashMap<>();
int queryBegin = link.indexOf('?');
Expand Down
Loading