Skip to content

Add provider config management operations. #433

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
merged 21 commits into from
Jun 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
392bf67
Add OIDC Auth provider config class (#397)
micahstairs Apr 27, 2020
a2fa782
Fix CreateRequest chaining and provider ID extraction. (#399)
micahstairs Apr 29, 2020
db81c06
Add operations to create and delete OIDC provider configs. (#400)
micahstairs Apr 29, 2020
424d9ce
Add getOidcProviderConfig operation. (#401)
micahstairs Apr 30, 2020
f728f63
Add operation to update OIDC provider configs. (#402)
micahstairs May 1, 2020
9052434
Add operation to list OIDC provider configs. (#404)
micahstairs May 5, 2020
580eedd
Move tenant-aware integration tests to separate class (#405)
micahstairs May 5, 2020
36c27be
Add missing listOidcProviderConfigs method. (#406)
micahstairs May 7, 2020
732a908
Add validation to provider config ID. (#410)
micahstairs May 12, 2020
c323314
fix(auth): Ensuring test user account cleanup with a Rule (#409)
hiranya911 May 12, 2020
7b55045
Rename OIDC delete operation and refactor integration tests. (#411)
micahstairs May 13, 2020
99b07bd
Make Javadoc comments consistent and validate provider ID in CreateRe…
micahstairs May 18, 2020
537b7a7
Add class for SAML provider config. (#419)
micahstairs May 18, 2020
0751674
Add operations to create and delete SAML provider configs. (#420)
micahstairs May 21, 2020
7732833
Add get operation for SAML provider configs. (#421)
micahstairs May 26, 2020
de24a2b
Add operation to update SAML provider configs. (#424)
micahstairs May 27, 2020
638240f
Add operation to list SAML provider configs. (#426)
micahstairs May 29, 2020
aa96da0
Finish implementing SAML provider config. (#428)
micahstairs Jun 3, 2020
0fa48b2
Move check in FirebaseUserManager.updateTenant outside CallableOperat…
micahstairs Jun 4, 2020
d32705a
Refactor shared code into UserTestUtils. (#432)
micahstairs Jun 4, 2020
a196aa4
Address Kevin's feedback.
micahstairs Jun 12, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions checkstyle.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@
<module name="FileTabCharacter">
<property name="eachLine" value="true"/>
</module>

<module name="SuppressionCommentFilter">
<property name="offCommentFormat" value="CSOFF\: ([\w\|]+)"/>
<property name="onCommentFormat" value="CSON\: ([\w\|]+)"/>
<property name="checkFormat" value="$1"/>
</module>

<module name="TreeWalker">
<module name="FileContentsHolder"/>
<module name="TreeWalker">
<module name="FileContentsHolder"/>
<module name="OuterTypeFilename"/>
<module name="IllegalTokenText">
<property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/>
Expand Down Expand Up @@ -229,6 +229,9 @@
<property name="allowedAnnotations" value="Override, Test"/>
<property name="allowThrowsTagsForSubclasses" value="true"/>
<property name="allowMissingJavadoc" value="true"/>
<!-- Setting this property helps avoid some strange errors. For more information, see -->
<!-- https://stackoverflow.com/questions/27938039/unable-to-get-class-information-for-checkstyle. -->
<property name="suppressLoadErrors" value="true"/>
</module>
<module name="MethodName">
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9_]*$"/>
Expand Down
602 changes: 567 additions & 35 deletions src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java

Large diffs are not rendered by default.

154 changes: 135 additions & 19 deletions src/main/java/com/google/firebase/auth/FirebaseUserManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
import com.google.firebase.auth.internal.GetAccountInfoRequest;
import com.google.firebase.auth.internal.GetAccountInfoResponse;
import com.google.firebase.auth.internal.HttpErrorResponse;
import com.google.firebase.auth.internal.ListOidcProviderConfigsResponse;
import com.google.firebase.auth.internal.ListSamlProviderConfigsResponse;
import com.google.firebase.auth.internal.ListTenantsResponse;
import com.google.firebase.auth.internal.UploadAccountResponse;
import com.google.firebase.internal.ApiClientUtils;
Expand All @@ -69,6 +71,7 @@
*/
class FirebaseUserManager {

static final String CONFIGURATION_NOT_FOUND_ERROR = "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 @@ -78,7 +81,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_ERROR)
.put("INSUFFICIENT_PERMISSION", "insufficient-permission")
.put("DUPLICATE_EMAIL", "email-already-exists")
.put("DUPLICATE_LOCAL_ID", "uid-already-exists")
Expand All @@ -97,6 +100,7 @@ class FirebaseUserManager {
.put("INVALID_DYNAMIC_LINK_DOMAIN", "invalid-dynamic-link-domain")
.build();

static final int MAX_LIST_PROVIDER_CONFIGS_RESULTS = 100;
static final int MAX_LIST_TENANTS_RESULTS = 1000;
static final int MAX_GET_ACCOUNTS_BATCH_SIZE = 100;
static final int MAX_DELETE_ACCOUNTS_BATCH_SIZE = 1000;
Expand All @@ -112,6 +116,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 @@ -126,15 +131,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);
checkArgument(!tenantId.isEmpty(), "Tenant ID must not be empty.");
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 @@ -295,20 +303,11 @@ Tenant createTenant(Tenant.CreateRequest request) throws FirebaseAuthException {

Tenant updateTenant(Tenant.UpdateRequest request) throws FirebaseAuthException {
Map<String, Object> properties = request.getProperties();
checkArgument(!properties.isEmpty(), "Tenant update must have at least one property set");
GenericUrl url = new GenericUrl(tenantMgtBaseUrl + getTenantUrlSuffix(request.getTenantId()));
url.put("updateMask", generateMask(properties));
url.put("updateMask", Joiner.on(",").join(generateMask(properties)));
return sendRequest("PATCH", url, properties, Tenant.class);
}

private static String generateMask(Map<String, Object> properties) {
// This implementation does not currently handle the case of nested properties. This is fine
// since we do not currently generate masks for any properties with nested values. When it
// comes time to implement this, we can check if a property has nested properties by checking
// if it is an instance of the Map class.
return Joiner.on(",").join(ImmutableSortedSet.copyOf(properties.keySet()));
}

void deleteTenant(String tenantId) throws FirebaseAuthException {
GenericUrl url = new GenericUrl(tenantMgtBaseUrl + getTenantUrlSuffix(tenantId));
sendRequest("DELETE", url, null, GenericJson.class);
Expand Down Expand Up @@ -366,11 +365,128 @@ 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");
url.set("oauthIdpConfigId", request.getProviderId());
return sendRequest("POST", url, request.getProperties(), OidcProviderConfig.class);
}

SamlProviderConfig createSamlProviderConfig(
SamlProviderConfig.CreateRequest request) throws FirebaseAuthException {
GenericUrl url = new GenericUrl(idpConfigMgtBaseUrl + "/inboundSamlConfigs");
url.set("inboundSamlConfigId", request.getProviderId());
return sendRequest("POST", url, request.getProperties(), SamlProviderConfig.class);
}

OidcProviderConfig updateOidcProviderConfig(OidcProviderConfig.UpdateRequest request)
throws FirebaseAuthException {
Map<String, Object> properties = request.getProperties();
GenericUrl url =
new GenericUrl(idpConfigMgtBaseUrl + getOidcUrlSuffix(request.getProviderId()));
url.put("updateMask", Joiner.on(",").join(generateMask(properties)));
return sendRequest("PATCH", url, properties, OidcProviderConfig.class);
}

SamlProviderConfig updateSamlProviderConfig(SamlProviderConfig.UpdateRequest request)
throws FirebaseAuthException {
Map<String, Object> properties = request.getProperties();
GenericUrl url =
new GenericUrl(idpConfigMgtBaseUrl + getSamlUrlSuffix(request.getProviderId()));
url.put("updateMask", Joiner.on(",").join(generateMask(properties)));
return sendRequest("PATCH", url, properties, SamlProviderConfig.class);
}

OidcProviderConfig getOidcProviderConfig(String providerId) throws FirebaseAuthException {
GenericUrl url = new GenericUrl(idpConfigMgtBaseUrl + getOidcUrlSuffix(providerId));
return sendRequest("GET", url, null, OidcProviderConfig.class);
}

SamlProviderConfig getSamlProviderConfig(String providerId) throws FirebaseAuthException {
GenericUrl url = new GenericUrl(idpConfigMgtBaseUrl + getSamlUrlSuffix(providerId));
return sendRequest("GET", url, null, SamlProviderConfig.class);
}

ListOidcProviderConfigsResponse listOidcProviderConfigs(int maxResults, String pageToken)
throws FirebaseAuthException {
ImmutableMap.Builder<String, Object> builder =
ImmutableMap.<String, Object>builder().put("pageSize", maxResults);
if (pageToken != null) {
checkArgument(!pageToken.equals(
ListProviderConfigsPage.END_OF_LIST), "Invalid end of list page token.");
builder.put("nextPageToken", pageToken);
}

GenericUrl url = new GenericUrl(idpConfigMgtBaseUrl + "/oauthIdpConfigs");
url.putAll(builder.build());
ListOidcProviderConfigsResponse response =
sendRequest("GET", url, null, ListOidcProviderConfigsResponse.class);
if (response == null) {
throw new FirebaseAuthException(INTERNAL_ERROR, "Failed to retrieve provider configs.");
}
return response;
}

ListSamlProviderConfigsResponse listSamlProviderConfigs(int maxResults, String pageToken)
throws FirebaseAuthException {
ImmutableMap.Builder<String, Object> builder =
ImmutableMap.<String, Object>builder().put("pageSize", maxResults);
if (pageToken != null) {
checkArgument(!pageToken.equals(
ListProviderConfigsPage.END_OF_LIST), "Invalid end of list page token.");
builder.put("nextPageToken", pageToken);
}

GenericUrl url = new GenericUrl(idpConfigMgtBaseUrl + "/inboundSamlConfigs");
url.putAll(builder.build());
ListSamlProviderConfigsResponse response =
sendRequest("GET", url, null, ListSamlProviderConfigsResponse.class);
if (response == null) {
throw new FirebaseAuthException(INTERNAL_ERROR, "Failed to retrieve provider configs.");
}
return response;
}

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

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

private static Set<String> generateMask(Map<String, Object> properties) {
ImmutableSortedSet.Builder<String> maskBuilder = ImmutableSortedSet.naturalOrder();
for (Map.Entry<String, Object> entry : properties.entrySet()) {
if (entry.getValue() instanceof Map) {
Set<String> childMask = generateMask((Map<String, Object>) entry.getValue());
for (String childProperty : childMask) {
maskBuilder.add(entry.getKey() + "." + childProperty);
}
} else {
maskBuilder.add(entry.getKey());
}
}
return maskBuilder.build();
}

private static String getTenantUrlSuffix(String tenantId) {
checkArgument(!Strings.isNullOrEmpty(tenantId));
checkArgument(!Strings.isNullOrEmpty(tenantId), "Tenant ID must not be null or empty.");
return "/tenants/" + tenantId;
}

private static String getOidcUrlSuffix(String providerId) {
checkArgument(!Strings.isNullOrEmpty(providerId), "Provider ID must not be null or empty.");
return "/oauthIdpConfigs/" + providerId;
}

private static String getSamlUrlSuffix(String providerId) {
checkArgument(!Strings.isNullOrEmpty(providerId), "Provider ID must not be null or empty.");
return "/inboundSamlConfigs/" + providerId;
}

private <T> T post(String path, Object content, Class<T> clazz) throws FirebaseAuthException {
checkArgument(!Strings.isNullOrEmpty(path), "path must not be null or empty");
checkNotNull(content, "content must not be null for POST requests");
Expand Down
Loading