Skip to content

Commit

Permalink
Add support for FedCM commands
Browse files Browse the repository at this point in the history
As specified in:
https://fedidcg.github.io/FedCM/#automation

Currently implemented in Chromium.
  • Loading branch information
cbiesinger committed May 29, 2023
1 parent edb838b commit 6f5a25a
Show file tree
Hide file tree
Showing 9 changed files with 274 additions and 0 deletions.
1 change: 1 addition & 0 deletions java/src/org/openqa/selenium/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ java_export(
name = "core",
srcs = glob([
"*.java",
"federatedcredentialmanagement/*.java",
"html5/*.java",
"internal/*.java",
"interactions/**/*.java",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package org.openqa.selenium.federatedcredentialmanagement;

import java.util.Map;

public class FederatedCredentialManagementAccount {
private final String accountId;
private final String email;
private final String name;
private final String givenName;
private final String pictureUrl;
private final String idpConfigUrl;
private final String loginState;
private final String termsOfServiceUrl;
private final String privacyPolicyUrl;

public static final String LOGIN_STATE_SIGNIN = "SignIn";
public static final String LOGIN_STATE_SIGNUP = "SignUp";

public FederatedCredentialManagementAccount(Map<String, String> dict) {
accountId = (String) dict.getOrDefault("accountId", null);
email = (String) dict.getOrDefault("email", null);
name = (String) dict.getOrDefault("name", null);
givenName = (String) dict.getOrDefault("givenName", null);
pictureUrl = (String) dict.getOrDefault("pictureUrl", null);
idpConfigUrl = (String) dict.getOrDefault("idpConfigUrl", null);
loginState = (String) dict.getOrDefault("loginState", null);
termsOfServiceUrl = (String) dict.getOrDefault("termsOfServiceUrl", null);
privacyPolicyUrl = (String) dict.getOrDefault("privacyPolicyUrl", null);
}

public String getAccountid() {
return accountId;
}

public String getEmail() {
return email;
}

public String getName() {
return name;
}

public String getGivenName() {
return givenName;
}

public String getPictureUrl() {
return pictureUrl;
}

public String getIdpConfigUrl() {
return idpConfigUrl;
}

public String getLoginState() {
return loginState;
}

public String getTermsOfServiceUrl() {
return termsOfServiceUrl;
}

public String getPrivacyPolicyUrl() {
return privacyPolicyUrl;
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package org.openqa.selenium.federatedcredentialmanagement;

import java.util.List;

public interface FederatedCredentialManagementDialog {

String DIALOG_TYPE_ACCOUNT_LIST = "AccountChooser";
String DIALOG_TYPE_AUTH_REAUTH = "AutoReauthn";

void cancelDialog();

void selectAccount(int index);

String getDialogType();

String getTitle();

String getSubtitle();

List<FederatedCredentialManagementAccount> getAccounts();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package org.openqa.selenium.federatedcredentialmanagement;

import org.openqa.selenium.Beta;

/**
* Used by classes to indicate that they can interact with FedCM dialogs.
*/
@Beta
public interface HasFederatedCredentialManagement {
// FedCM by default delays promise resolution in failure cases for privacy
// reasons (https://fedidcg.github.io/FedCM/#ref-for-setdelayenabled);
// this function allows turning it off to let tests run faster where this
// is not relevant.
void setDelayEnabled(boolean enabled);

// If a user agent triggers a cooldown when the account chooser is dismissed,
// this function resets that cooldown so that the dialog can be triggered
// again immediately.
void resetCooldown();

// Gets the currently open FedCM dialog, or null if there is no dialog.
FederatedCredentialManagementDialog getFederatedCredentialManagementDialog();
}

19 changes: 19 additions & 0 deletions java/src/org/openqa/selenium/remote/DriverCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,15 @@ public interface DriverCommand {
String REMOVE_CREDENTIAL = "removeCredential";
String REMOVE_ALL_CREDENTIALS = "removeAllCredentials";
String SET_USER_VERIFIED = "setUserVerified";
// Federated Credential Management API
// https://fedidcg.github.io/FedCM/#automation
String CANCEL_DIALOG = "cancelDialog";
String SELECT_ACCOUNT = "selectAccount";
String GET_ACCOUNTS = "getAccounts";
String GET_FEDCM_TITLE = "getFedCmTitle";
String GET_FEDCM_DIALOG_TYPE = "getFedCmDialogType";
String SET_DELAY_ENABLED = "setDelayEnabled";
String RESET_COOLDOWN = "resetCooldown";

static CommandPayload NEW_SESSION(Capabilities capabilities) {
Require.nonNull("Capabilities", capabilities);
Expand Down Expand Up @@ -401,4 +410,14 @@ static CommandPayload SET_CURRENT_WINDOW_SIZE(Dimension targetSize) {
SET_CURRENT_WINDOW_SIZE,
ImmutableMap.of("width", targetSize.width, "height", targetSize.height));
}

static CommandPayload SELECT_ACCOUNT(int index) {
return new CommandPayload(
SELECT_ACCOUNT, ImmutableMap.of("accountIndex", index));
}

static CommandPayload SET_DELAY_ENABLED(boolean enabled) {
return new CommandPayload(
SET_DELAY_ENABLED, ImmutableMap.of("enabled", enabled));
}
}
26 changes: 26 additions & 0 deletions java/src/org/openqa/selenium/remote/RemoteWebDriver.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import org.openqa.selenium.ImmutableCapabilities;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.MutableCapabilities;
import org.openqa.selenium.NoAlertPresentException;
import org.openqa.selenium.NoSuchFrameException;
import org.openqa.selenium.NoSuchWindowException;
import org.openqa.selenium.OutputType;
Expand All @@ -72,6 +73,8 @@
import org.openqa.selenium.bidi.HasBiDi;
import org.openqa.selenium.devtools.DevTools;
import org.openqa.selenium.devtools.HasDevTools;
import org.openqa.selenium.federatedcredentialmanagement.HasFederatedCredentialManagement;
import org.openqa.selenium.federatedcredentialmanagement.FederatedCredentialManagementDialog;
import org.openqa.selenium.interactions.Interactive;
import org.openqa.selenium.interactions.Sequence;
import org.openqa.selenium.internal.Require;
Expand All @@ -97,6 +100,7 @@ public class RemoteWebDriver
implements WebDriver,
JavascriptExecutor,
HasCapabilities,
HasFederatedCredentialManagement,
HasVirtualAuthenticator,
Interactive,
PrintsPage,
Expand Down Expand Up @@ -622,6 +626,28 @@ public void removeVirtualAuthenticator(VirtualAuthenticator authenticator) {
ImmutableMap.of("authenticatorId", authenticator.getId()));
}

@Override
public void setDelayEnabled(boolean enabled) {
execute(DriverCommand.SET_DELAY_ENABLED(enabled));
}

@Override
public void resetCooldown() {
execute(DriverCommand.RESET_COOLDOWN);
}

@Override
public FederatedCredentialManagementDialog getFederatedCredentialManagementDialog() {
FederatedCredentialManagementDialog dialog = new FedCmDialogImpl(executeMethod);
try {
// As long as this does not throw, we're good.
dialog.getDialogType();
return dialog;
} catch (NoAlertPresentException e) {
return null;
}
}

/**
* Override this to be notified at key points in the execution of a command.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import static org.openqa.selenium.remote.DriverCommand.ADD_COOKIE;
import static org.openqa.selenium.remote.DriverCommand.ADD_CREDENTIAL;
import static org.openqa.selenium.remote.DriverCommand.ADD_VIRTUAL_AUTHENTICATOR;
import static org.openqa.selenium.remote.DriverCommand.CANCEL_DIALOG;
import static org.openqa.selenium.remote.DriverCommand.CLEAR_ELEMENT;
import static org.openqa.selenium.remote.DriverCommand.CLICK_ELEMENT;
import static org.openqa.selenium.remote.DriverCommand.CLOSE;
Expand All @@ -39,6 +40,7 @@
import static org.openqa.selenium.remote.DriverCommand.FIND_ELEMENTS;
import static org.openqa.selenium.remote.DriverCommand.FULLSCREEN_CURRENT_WINDOW;
import static org.openqa.selenium.remote.DriverCommand.GET;
import static org.openqa.selenium.remote.DriverCommand.GET_ACCOUNTS;
import static org.openqa.selenium.remote.DriverCommand.GET_ALL_COOKIES;
import static org.openqa.selenium.remote.DriverCommand.GET_ALL_SESSIONS;
import static org.openqa.selenium.remote.DriverCommand.GET_APP_CACHE_STATUS;
Expand All @@ -55,6 +57,8 @@
import static org.openqa.selenium.remote.DriverCommand.GET_ELEMENT_TAG_NAME;
import static org.openqa.selenium.remote.DriverCommand.GET_ELEMENT_TEXT;
import static org.openqa.selenium.remote.DriverCommand.GET_ELEMENT_VALUE_OF_CSS_PROPERTY;
import static org.openqa.selenium.remote.DriverCommand.GET_FEDCM_DIALOG_TYPE;
import static org.openqa.selenium.remote.DriverCommand.GET_FEDCM_TITLE;
import static org.openqa.selenium.remote.DriverCommand.GET_LOCATION;
import static org.openqa.selenium.remote.DriverCommand.GET_LOG;
import static org.openqa.selenium.remote.DriverCommand.GET_NETWORK_CONNECTION;
Expand All @@ -75,10 +79,13 @@
import static org.openqa.selenium.remote.DriverCommand.REMOVE_ALL_CREDENTIALS;
import static org.openqa.selenium.remote.DriverCommand.REMOVE_CREDENTIAL;
import static org.openqa.selenium.remote.DriverCommand.REMOVE_VIRTUAL_AUTHENTICATOR;
import static org.openqa.selenium.remote.DriverCommand.RESET_COOLDOWN;
import static org.openqa.selenium.remote.DriverCommand.SCREENSHOT;
import static org.openqa.selenium.remote.DriverCommand.SELECT_ACCOUNT;
import static org.openqa.selenium.remote.DriverCommand.SEND_KEYS_TO_ELEMENT;
import static org.openqa.selenium.remote.DriverCommand.SET_ALERT_CREDENTIALS;
import static org.openqa.selenium.remote.DriverCommand.SET_BROWSER_ONLINE;
import static org.openqa.selenium.remote.DriverCommand.SET_DELAY_ENABLED;
import static org.openqa.selenium.remote.DriverCommand.SET_LOCATION;
import static org.openqa.selenium.remote.DriverCommand.SET_NETWORK_CONNECTION;
import static org.openqa.selenium.remote.DriverCommand.SET_SCREEN_ORIENTATION;
Expand Down Expand Up @@ -220,6 +227,16 @@ public AbstractHttpCommandCodec() {
defineCommand(REMOVE_CREDENTIAL, delete(webauthnId + "/credentials/:credentialId"));
defineCommand(REMOVE_ALL_CREDENTIALS, delete(webauthnId + "/credentials"));
defineCommand(SET_USER_VERIFIED, post(webauthnId + "/uv"));

// Federated Credential Management API
String fedcm = sessionId + "/fedcm";
defineCommand(CANCEL_DIALOG, post(fedcm + "/canceldialog"));
defineCommand(SELECT_ACCOUNT, post(fedcm + "/selectaccount"));
defineCommand(GET_ACCOUNTS, get(fedcm + "/accountlist"));
defineCommand(GET_FEDCM_TITLE, get(fedcm + "/gettitle"));
defineCommand(GET_FEDCM_DIALOG_TYPE, get(fedcm + "/getdialogtype"));
defineCommand(SET_DELAY_ENABLED, post(fedcm + "/setdelayenabled"));
defineCommand(RESET_COOLDOWN, post(fedcm + "/resetCooldown"));
}

protected static CommandSpec delete(String path) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public HandlersForTests(String hostname, int port, Path tempPageDir) {
.addHeader("Content-Type", MediaType.HTML_UTF_8.toString())
.setContent(Contents.string("<h1>authorized</h1>", UTF_8)))
.with(new BasicAuthenticationFilter("test", "test")),
Route.get("/.well-known/web-identity").to(WellKnownWebIdentityHandler::new),
Route.get("/echo").to(EchoHandler::new),
Route.get("/cookie").to(CookieHandler::new),
Route.get("/encoding").to(EncodingHandler::new),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package org.openqa.selenium.environment.webserver;

import static java.nio.charset.StandardCharsets.UTF_8;

import java.io.UncheckedIOException;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.openqa.selenium.remote.http.Contents;
import org.openqa.selenium.remote.http.HttpHandler;
import org.openqa.selenium.remote.http.HttpRequest;
import org.openqa.selenium.remote.http.HttpResponse;
import org.openqa.selenium.remote.http.UrlPath;

class WellKnownWebIdentityHandler implements HttpHandler {

private static final String RESPONSE_STRING =
"\"{provider_urls\": [\"%s\"]}";

@Override
public HttpResponse execute(HttpRequest req) throws UncheckedIOException {
HttpResponse response = new HttpResponse();
response.setHeader("Content-Type", "application/json");
response.setHeader("Cache-Control", "no-store");
String targetLocation = UrlPath.relativeToContext(req, "/fedcm/fedcm.json");

response.setContent(
Contents.string(String.format(RESPONSE_STRING, targetLocation), UTF_8));

return response;
}
}

0 comments on commit 6f5a25a

Please sign in to comment.