Skip to content

Commit 6962561

Browse files
authored
feat(auth): enables OIDC auth code flow (#532)
* feat: enables OIDC auth code flow * fix: remove GenericJson from public API surface * fix: remove duplication * fix: camel case for test cases * fix: remove import
1 parent 4170ed9 commit 6962561

File tree

7 files changed

+280
-17
lines changed

7 files changed

+280
-17
lines changed

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

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@
1818

1919
import static com.google.common.base.Preconditions.checkArgument;
2020

21+
import com.google.api.client.json.GenericJson;
2122
import com.google.api.client.util.Key;
2223
import com.google.common.base.Strings;
24+
import java.util.HashMap;
25+
import java.util.Map;
2326

2427
/**
2528
* Contains metadata associated with an OIDC Auth provider.
@@ -31,17 +34,35 @@ public final class OidcProviderConfig extends ProviderConfig {
3134
@Key("clientId")
3235
private String clientId;
3336

37+
@Key("clientSecret")
38+
private String clientSecret;
39+
3440
@Key("issuer")
3541
private String issuer;
3642

43+
@Key("responseType")
44+
private GenericJson responseType;
45+
3746
public String getClientId() {
3847
return clientId;
3948
}
4049

50+
public String getClientSecret() {
51+
return clientSecret;
52+
}
53+
4154
public String getIssuer() {
4255
return issuer;
4356
}
4457

58+
public boolean isCodeResponseType() {
59+
return (responseType.containsKey("code") && (boolean) responseType.get("code"));
60+
}
61+
62+
public boolean isIdTokenResponseType() {
63+
return (responseType.containsKey("idToken") && (boolean) responseType.get("idToken"));
64+
}
65+
4566
/**
4667
* Returns a new {@link UpdateRequest}, which can be used to update the attributes of this
4768
* provider config.
@@ -58,6 +79,13 @@ static void checkOidcProviderId(String providerId) {
5879
"Invalid OIDC provider ID (must be prefixed with 'oidc.'): " + providerId);
5980
}
6081

82+
static Map<String, Boolean> ensureResponseType(Map<String,Object> properties) {
83+
if (properties.get("responseType") == null) {
84+
properties.put("responseType", new HashMap<String, Boolean>());
85+
}
86+
return (Map<String, Boolean>) properties.get("responseType");
87+
}
88+
6189
/**
6290
* A specification class for creating a new OIDC Auth provider.
6391
*
@@ -99,6 +127,19 @@ public CreateRequest setClientId(String clientId) {
99127
return this;
100128
}
101129

130+
/**
131+
* Sets the client secret for the new provider. This is required for the code flow.
132+
*
133+
* @param clientSecret A non-null, non-empty client secret string.
134+
* @throws IllegalArgumentException If the client secret is null or empty.
135+
*/
136+
public CreateRequest setClientSecret(String clientSecret) {
137+
checkArgument(!Strings.isNullOrEmpty(clientSecret),
138+
"Client Secret must not be null or empty.");
139+
properties.put("clientSecret", clientSecret);
140+
return this;
141+
}
142+
102143
/**
103144
* Sets the issuer for the new provider.
104145
*
@@ -113,6 +154,36 @@ public CreateRequest setIssuer(String issuer) {
113154
return this;
114155
}
115156

157+
/**
158+
* Sets whether to enable the code response flow for the new provider. By default, this is not
159+
* enabled if no response type is specified.
160+
*
161+
* <p>A client secret must be set for this response type.
162+
*
163+
* <p>Having both the code and ID token response flows is currently not supported.
164+
*
165+
* @param enabled A boolean signifying whether the code response type is supported.
166+
*/
167+
public CreateRequest setCodeResponseType(boolean enabled) {
168+
Map<String, Boolean> map = ensureResponseType(properties);
169+
map.put("code", enabled);
170+
return this;
171+
}
172+
173+
/**
174+
* Sets whether to enable the ID token response flow for the new provider. By default, this is
175+
* enabled if no response type is specified.
176+
*
177+
* <p>Having both the code and ID token response flows is currently not supported.
178+
*
179+
* @param enabled A boolean signifying whether the ID token response type is supported.
180+
*/
181+
public CreateRequest setIdTokenResponseType(boolean enabled) {
182+
Map<String, Boolean> map = ensureResponseType(properties);
183+
map.put("idToken", enabled);
184+
return this;
185+
}
186+
116187
CreateRequest getThis() {
117188
return this;
118189
}
@@ -156,6 +227,19 @@ public UpdateRequest setClientId(String clientId) {
156227
return this;
157228
}
158229

230+
/**
231+
* Sets the client secret for the new provider. This is required for the code flow.
232+
*
233+
* @param clientSecret A non-null, non-empty client secret string.
234+
* @throws IllegalArgumentException If the client secret is null or empty.
235+
*/
236+
public UpdateRequest setClientSecret(String clientSecret) {
237+
checkArgument(!Strings.isNullOrEmpty(clientSecret),
238+
"Client Secret must not be null or empty.");
239+
properties.put("clientSecret", clientSecret);
240+
return this;
241+
}
242+
159243
/**
160244
* Sets the issuer for the existing provider.
161245
*
@@ -170,6 +254,36 @@ public UpdateRequest setIssuer(String issuer) {
170254
return this;
171255
}
172256

257+
/**
258+
* Sets whether to enable the code response flow for the new provider. By default, this is not
259+
* enabled if no response type is specified.
260+
*
261+
* <p>A client secret must be set for this response type.
262+
*
263+
* <p>Having both the code and ID token response flows is currently not supported.
264+
*
265+
* @param enabled A boolean signifying whether the code response type is supported.
266+
*/
267+
public UpdateRequest setCodeResponseType(boolean enabled) {
268+
Map<String, Boolean> map = ensureResponseType(properties);
269+
map.put("code", enabled);
270+
return this;
271+
}
272+
273+
/**
274+
* Sets whether to enable the ID token response flow for the new provider. By default, this is
275+
* enabled if no response type is specified.
276+
*
277+
* <p>Having both the code and ID token response flows is currently not supported.
278+
*
279+
* @param enabled A boolean signifying whether the ID token response type is supported.
280+
*/
281+
public UpdateRequest setIdTokenResponseType(boolean enabled) {
282+
Map<String, Boolean> map = ensureResponseType(properties);
283+
map.put("idToken", enabled);
284+
return this;
285+
}
286+
173287
UpdateRequest getThis() {
174288
return this;
175289
}

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

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -792,34 +792,51 @@ public void testOidcProviderConfigLifecycle() throws Exception {
792792
.setDisplayName("DisplayName")
793793
.setEnabled(true)
794794
.setClientId("ClientId")
795-
.setIssuer("https://oidc.com/issuer"));
795+
.setClientSecret("ClientSecret")
796+
.setIssuer("https://oidc.com/issuer")
797+
.setCodeResponseType(true)
798+
.setIdTokenResponseType(false));
799+
796800
assertEquals(providerId, config.getProviderId());
797801
assertEquals("DisplayName", config.getDisplayName());
798802
assertTrue(config.isEnabled());
799803
assertEquals("ClientId", config.getClientId());
804+
assertEquals("ClientSecret", config.getClientSecret());
800805
assertEquals("https://oidc.com/issuer", config.getIssuer());
806+
assertTrue(config.isCodeResponseType());
807+
assertFalse(config.isIdTokenResponseType());
801808

802809
// Get provider config
803810
config = auth.getOidcProviderConfigAsync(providerId).get();
804811
assertEquals(providerId, config.getProviderId());
805812
assertEquals("DisplayName", config.getDisplayName());
806813
assertTrue(config.isEnabled());
807814
assertEquals("ClientId", config.getClientId());
815+
assertEquals("ClientSecret", config.getClientSecret());
808816
assertEquals("https://oidc.com/issuer", config.getIssuer());
817+
assertTrue(config.isCodeResponseType());
818+
assertFalse(config.isIdTokenResponseType());
809819

810820
// Update provider config
811821
OidcProviderConfig.UpdateRequest updateRequest =
812822
new OidcProviderConfig.UpdateRequest(providerId)
813823
.setDisplayName("NewDisplayName")
814824
.setEnabled(false)
815825
.setClientId("NewClientId")
816-
.setIssuer("https://oidc.com/new-issuer");
826+
.setClientSecret("NewClientSecret")
827+
.setIssuer("https://oidc.com/new-issuer")
828+
.setCodeResponseType(false)
829+
.setIdTokenResponseType(true);
830+
817831
config = auth.updateOidcProviderConfigAsync(updateRequest).get();
818832
assertEquals(providerId, config.getProviderId());
819833
assertEquals("NewDisplayName", config.getDisplayName());
820834
assertFalse(config.isEnabled());
821835
assertEquals("NewClientId", config.getClientId());
836+
assertEquals("NewClientSecret", config.getClientSecret());
822837
assertEquals("https://oidc.com/new-issuer", config.getIssuer());
838+
assertTrue(config.isIdTokenResponseType());
839+
assertFalse(config.isCodeResponseType());
823840

824841
// Delete provider config
825842
temporaryProviderConfig.deleteOidcProviderConfig(providerId);

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

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1636,7 +1636,10 @@ public void testCreateOidcProvider() throws Exception {
16361636
.setDisplayName("DISPLAY_NAME")
16371637
.setEnabled(true)
16381638
.setClientId("CLIENT_ID")
1639-
.setIssuer("https://oidc.com/issuer");
1639+
.setClientSecret("CLIENT_SECRET")
1640+
.setIssuer("https://oidc.com/issuer")
1641+
.setCodeResponseType(true)
1642+
.setIdTokenResponseType(true);
16401643

16411644
OidcProviderConfig config = FirebaseAuth.getInstance().createOidcProviderConfig(createRequest);
16421645

@@ -1647,7 +1650,13 @@ public void testCreateOidcProvider() throws Exception {
16471650
assertEquals("DISPLAY_NAME", parsed.get("displayName"));
16481651
assertTrue((boolean) parsed.get("enabled"));
16491652
assertEquals("CLIENT_ID", parsed.get("clientId"));
1653+
assertEquals("CLIENT_SECRET", parsed.get("clientSecret"));
16501654
assertEquals("https://oidc.com/issuer", parsed.get("issuer"));
1655+
1656+
Map<String, Boolean> responseType = (Map<String, Boolean>) parsed.get("responseType");
1657+
assertTrue(responseType.get("code"));
1658+
assertTrue(responseType.get("idToken"));
1659+
16511660
GenericUrl url = interceptor.getResponse().getRequest().getUrl();
16521661
assertEquals("oidc.provider-id", url.getFirst("oauthIdpConfigId"));
16531662
}
@@ -1661,7 +1670,10 @@ public void testCreateOidcProviderAsync() throws Exception {
16611670
.setDisplayName("DISPLAY_NAME")
16621671
.setEnabled(true)
16631672
.setClientId("CLIENT_ID")
1664-
.setIssuer("https://oidc.com/issuer");
1673+
.setClientSecret("CLIENT_SECRET")
1674+
.setIssuer("https://oidc.com/issuer")
1675+
.setCodeResponseType(true)
1676+
.setIdTokenResponseType(true);
16651677

16661678
OidcProviderConfig config =
16671679
FirebaseAuth.getInstance().createOidcProviderConfigAsync(createRequest).get();
@@ -1673,7 +1685,13 @@ public void testCreateOidcProviderAsync() throws Exception {
16731685
assertEquals("DISPLAY_NAME", parsed.get("displayName"));
16741686
assertTrue((boolean) parsed.get("enabled"));
16751687
assertEquals("CLIENT_ID", parsed.get("clientId"));
1688+
assertEquals("CLIENT_SECRET", parsed.get("clientSecret"));
16761689
assertEquals("https://oidc.com/issuer", parsed.get("issuer"));
1690+
1691+
Map<String, Boolean> responseType = (Map<String, Boolean>) parsed.get("responseType");
1692+
assertTrue(responseType.get("code"));
1693+
assertTrue(responseType.get("idToken"));
1694+
16771695
GenericUrl url = interceptor.getResponse().getRequest().getUrl();
16781696
assertEquals("oidc.provider-id", url.getFirst("oauthIdpConfigId"));
16791697
}
@@ -1876,7 +1894,10 @@ public void testTenantAwareUpdateOidcProvider() throws Exception {
18761894
.setDisplayName("DISPLAY_NAME")
18771895
.setEnabled(true)
18781896
.setClientId("CLIENT_ID")
1879-
.setIssuer("https://oidc.com/issuer");
1897+
.setClientSecret("CLIENT_SECRET")
1898+
.setIssuer("https://oidc.com/issuer")
1899+
.setCodeResponseType(true)
1900+
.setIdTokenResponseType(true);
18801901

18811902
OidcProviderConfig config = tenantAwareAuth.updateOidcProviderConfig(request);
18821903

@@ -1885,12 +1906,18 @@ public void testTenantAwareUpdateOidcProvider() throws Exception {
18851906
String expectedUrl = TENANTS_BASE_URL + "/TENANT_ID/oauthIdpConfigs/oidc.provider-id";
18861907
checkUrl(interceptor, "PATCH", expectedUrl);
18871908
GenericUrl url = interceptor.getResponse().getRequest().getUrl();
1888-
assertEquals("clientId,displayName,enabled,issuer", url.getFirst("updateMask"));
1909+
assertEquals("clientId,clientSecret,displayName,enabled,issuer,responseType.code,"
1910+
+ "responseType.idToken", url.getFirst("updateMask"));
18891911
GenericJson parsed = parseRequestContent(interceptor);
18901912
assertEquals("DISPLAY_NAME", parsed.get("displayName"));
18911913
assertTrue((boolean) parsed.get("enabled"));
18921914
assertEquals("CLIENT_ID", parsed.get("clientId"));
1915+
assertEquals("CLIENT_SECRET", parsed.get("clientSecret"));
18931916
assertEquals("https://oidc.com/issuer", parsed.get("issuer"));
1917+
1918+
Map<String, Boolean> responseType = (Map<String, Boolean>) parsed.get("responseType");
1919+
assertTrue(responseType.get("code"));
1920+
assertTrue(responseType.get("idToken"));
18941921
}
18951922

18961923
@Test
@@ -2969,7 +2996,10 @@ private static void checkOidcProviderConfig(OidcProviderConfig config, String pr
29692996
assertEquals("DISPLAY_NAME", config.getDisplayName());
29702997
assertTrue(config.isEnabled());
29712998
assertEquals("CLIENT_ID", config.getClientId());
2999+
assertEquals("CLIENT_SECRET", config.getClientSecret());
29723000
assertEquals("https://oidc.com/issuer", config.getIssuer());
3001+
assertTrue(config.isCodeResponseType());
3002+
assertFalse(config.isIdTokenResponseType());
29733003
}
29743004

29753005
private static void checkSamlProviderConfig(SamlProviderConfig config, String providerId) {
@@ -3001,6 +3031,5 @@ private static void checkUrl(TestResponseInterceptor interceptor, String method,
30013031
private interface UserManagerOp {
30023032
void call(FirebaseAuth auth) throws Exception;
30033033
}
3004-
30053034
}
30063035

0 commit comments

Comments
 (0)