parent
d6abade872
commit
85240c9606
16 changed files with 12 additions and 1699 deletions
|
@ -1,694 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed 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.keycloak.adapters.installed;
|
||||
|
||||
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||
import org.keycloak.adapters.ServerRequest;
|
||||
import org.keycloak.common.util.Base64;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.jose.jwe.*;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.ws.rs.client.Client;
|
||||
import javax.ws.rs.client.Entity;
|
||||
import javax.ws.rs.client.WebTarget;
|
||||
import javax.ws.rs.core.Form;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.KeySpec;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* All kcinit commands that take input ask for
|
||||
* <p>
|
||||
* 1. . kcinit
|
||||
* - setup and export KC_SESSION_KEY env var if not set.
|
||||
* - checks to see if master token valid, refresh is possible, exit if token valid
|
||||
* - performs command line login
|
||||
* - stores master token for master client
|
||||
* 2. app.sh is a wrapper for app cli.
|
||||
* - token=`kcinit token app`
|
||||
* - checks to see if token for app client has been fetched, refresh if valid, output token to sys.out if exists
|
||||
* - if no token, login. Prompts go to stderr.
|
||||
* - pass token as cmd line param to app or as environment variable.
|
||||
* <p>
|
||||
* 3. kcinit password {password}
|
||||
* - outputs password key that is used for encryption.
|
||||
* - can be used in .bashrc as export KC_SESSSION_KEY=`kcinit password {password}` or just set it in .bat file
|
||||
* <p>
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KcinitDriver {
|
||||
|
||||
public static final String KC_SESSION_KEY = "KC_SESSION_KEY";
|
||||
public static final String KC_LOGIN_CONFIG_PATH = "KC_LOGIN_CONFIG_PATH";
|
||||
protected Map<String, String> config;
|
||||
protected boolean debug = true;
|
||||
|
||||
protected static byte[] salt = new byte[]{-4, 88, 66, -101, 78, -94, 21, 105};
|
||||
|
||||
String[] args = null;
|
||||
|
||||
protected boolean forceLogin;
|
||||
protected boolean browserLogin;
|
||||
|
||||
public void mainCmd(String[] args) throws Exception {
|
||||
|
||||
this.args = args;
|
||||
|
||||
|
||||
if (args.length == 0) {
|
||||
printHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
if (args[0].equalsIgnoreCase("token")) {
|
||||
//System.err.println("executing token");
|
||||
token();
|
||||
} else if (args[0].equalsIgnoreCase("login")) {
|
||||
login();
|
||||
} else if (args[0].equalsIgnoreCase("logout")) {
|
||||
logout();
|
||||
} else if (args[0].equalsIgnoreCase("env")) {
|
||||
System.out.println(System.getenv().toString());
|
||||
} else if (args[0].equalsIgnoreCase("install")) {
|
||||
install();
|
||||
} else if (args[0].equalsIgnoreCase("uninstall")) {
|
||||
uninstall();
|
||||
} else if (args[0].equalsIgnoreCase("password")) {
|
||||
passwordKey();
|
||||
} else {
|
||||
KeycloakInstalled.console().writer().println("Unknown command: " + args[0]);
|
||||
KeycloakInstalled.console().writer().println();
|
||||
printHelp();
|
||||
}
|
||||
}
|
||||
|
||||
public String getHome() {
|
||||
String home = System.getenv("HOME");
|
||||
if (home == null) {
|
||||
home = System.getProperty("HOME");
|
||||
if (home == null) {
|
||||
home = Paths.get("").toAbsolutePath().normalize().toString();
|
||||
}
|
||||
}
|
||||
return home;
|
||||
}
|
||||
|
||||
public void passwordKey() {
|
||||
if (args.length < 2) {
|
||||
printHelp();
|
||||
System.exit(1);
|
||||
}
|
||||
String password = args[1];
|
||||
try {
|
||||
String encodedKey = generateEncryptionKey(password);
|
||||
System.out.printf(encodedKey);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
protected String generateEncryptionKey(String password) throws NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
|
||||
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 100, 128);
|
||||
SecretKey tmp = factory.generateSecret(spec);
|
||||
byte[] aeskey = tmp.getEncoded();
|
||||
return Base64.encodeBytes(aeskey);
|
||||
}
|
||||
|
||||
public JWE createJWE() {
|
||||
String key = getEncryptionKey();
|
||||
if (key == null) {
|
||||
throw new RuntimeException(KC_SESSION_KEY + " env var not set");
|
||||
}
|
||||
byte[] aesKey = null;
|
||||
try {
|
||||
aesKey = Base64.decode(key.getBytes(StandardCharsets.UTF_8));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("invalid " + KC_SESSION_KEY + "env var");
|
||||
}
|
||||
|
||||
JWE jwe = new JWE();
|
||||
final SecretKey aesSecret = new SecretKeySpec(aesKey, "AES");
|
||||
jwe.getKeyStorage()
|
||||
.setDecryptionKey(aesSecret);
|
||||
return jwe;
|
||||
}
|
||||
|
||||
protected String encryptionKey;
|
||||
|
||||
protected String getEncryptionKey() {
|
||||
if (encryptionKey != null) return encryptionKey;
|
||||
return System.getenv(KC_SESSION_KEY);
|
||||
}
|
||||
|
||||
public String encrypt(String payload) {
|
||||
JWE jwe = createJWE();
|
||||
JWEHeader jweHeader = new JWEHeader(JWEConstants.A128KW, JWEConstants.A128CBC_HS256, null);
|
||||
jwe.header(jweHeader).content(payload.getBytes(StandardCharsets.UTF_8));
|
||||
try {
|
||||
return jwe.encodeJwe();
|
||||
} catch (JWEException e) {
|
||||
throw new RuntimeException("cannot encrypt payload", e);
|
||||
}
|
||||
}
|
||||
|
||||
public String decrypt(String encoded) {
|
||||
JWE jwe = createJWE();
|
||||
try {
|
||||
jwe.verifyAndDecodeJwe(encoded);
|
||||
byte[] content = jwe.getContent();
|
||||
if (content == null) return null;
|
||||
return new String(content, StandardCharsets.UTF_8);
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException("cannot decrypt payload", ex);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static String getenv(String name, String defaultValue) {
|
||||
String val = System.getenv(name);
|
||||
return val == null ? defaultValue : val;
|
||||
}
|
||||
|
||||
public File getConfigDirectory() {
|
||||
return Paths.get(getHome(), getenv(KC_LOGIN_CONFIG_PATH, ".keycloak"), "kcinit").toFile();
|
||||
}
|
||||
|
||||
|
||||
public File getConfigFile() {
|
||||
return Paths.get(getHome(), getenv(KC_LOGIN_CONFIG_PATH, ".keycloak"), "kcinit", "config.json").toFile();
|
||||
}
|
||||
|
||||
public File getTokenFilePath(String client) {
|
||||
return Paths.get(getHome(), getenv(KC_LOGIN_CONFIG_PATH, ".keycloak"), "kcinit", "tokens", client).toFile();
|
||||
}
|
||||
|
||||
public File getTokenDirectory() {
|
||||
return Paths.get(getHome(), getenv(KC_LOGIN_CONFIG_PATH, ".keycloak"), "kcinit", "tokens").toFile();
|
||||
}
|
||||
|
||||
protected boolean encrypted = false;
|
||||
|
||||
protected void checkEnv() {
|
||||
File configFile = getConfigFile();
|
||||
if (!configFile.exists()) {
|
||||
KeycloakInstalled.console().writer().println("You have not configured kcinit. Please run 'kcinit install' to configure.");
|
||||
System.exit(1);
|
||||
}
|
||||
byte[] data = new byte[0];
|
||||
try {
|
||||
data = readFileRaw(configFile);
|
||||
} catch (IOException e) {
|
||||
|
||||
}
|
||||
if (data == null) {
|
||||
KeycloakInstalled.console().writer().println("Config file unreadable. Please run 'kcinit install' to configure.");
|
||||
System.exit(1);
|
||||
|
||||
}
|
||||
String encodedJwe = new String(data, StandardCharsets.UTF_8);
|
||||
|
||||
if (encodedJwe.contains("realm")) {
|
||||
encrypted = false;
|
||||
return;
|
||||
} else {
|
||||
encrypted = true;
|
||||
}
|
||||
|
||||
if (System.getenv(KC_SESSION_KEY) == null) {
|
||||
promptLocalPassword();
|
||||
}
|
||||
}
|
||||
|
||||
protected void promptLocalPassword() {
|
||||
String password = KeycloakInstalled.console().passwordPrompt("Enter password to unlock kcinit config files: ");
|
||||
try {
|
||||
encryptionKey = generateEncryptionKey(password);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected String readFile(File fp) {
|
||||
try {
|
||||
byte[] data = readFileRaw(fp);
|
||||
if (data == null) return null;
|
||||
String file = new String(data, StandardCharsets.UTF_8);
|
||||
if (!encrypted) {
|
||||
return file;
|
||||
}
|
||||
String decrypted = decrypt(file);
|
||||
if (decrypted == null)
|
||||
throw new RuntimeException("Unable to decrypt file. Did you set your local password correctly?");
|
||||
return decrypted;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("failed to decrypt file: " + fp.getAbsolutePath() + " Did you set your local password correctly?", e);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
protected byte[] readFileRaw(File fp) throws IOException {
|
||||
if (!fp.exists()) return null;
|
||||
try (FileInputStream fis = new FileInputStream(fp)) {
|
||||
byte[] data = new byte[(int) fp.length()];
|
||||
fis.read(data);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeFile(File fp, String payload) {
|
||||
try {
|
||||
String data = payload;
|
||||
if (encrypted) data = encrypt(payload);
|
||||
FileOutputStream fos = new FileOutputStream(fp);
|
||||
fos.write(data.getBytes(StandardCharsets.UTF_8));
|
||||
fos.flush();
|
||||
fos.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void install() {
|
||||
if (getEncryptionKey() == null) {
|
||||
if (KeycloakInstalled.console().confirm("Do you want to protect tokens stored locally with a password? (y/n): ")) {
|
||||
String password = "p";
|
||||
String confirm = "c";
|
||||
do {
|
||||
password = KeycloakInstalled.console().passwordPrompt("Enter local password: ");
|
||||
confirm = KeycloakInstalled.console().passwordPrompt("Confirm local password: ");
|
||||
if (!password.equals(confirm)) {
|
||||
KeycloakInstalled.console().writer().println();
|
||||
KeycloakInstalled.console().writer().println("Confirmation does not match. Try again.");
|
||||
KeycloakInstalled.console().writer().println();
|
||||
}
|
||||
} while (!password.equals(confirm));
|
||||
try {
|
||||
this.encrypted = true;
|
||||
this.encryptionKey = generateEncryptionKey(password);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!KeycloakInstalled.console().confirm("KC_SESSION_KEY env var already set. Do you want to use this as your local encryption key? (y/n): ")) {
|
||||
KeycloakInstalled.console().writer().println("Unset KC_SESSION_KEY env var and run again");
|
||||
System.exit(1);
|
||||
}
|
||||
this.encrypted = true;
|
||||
this.encryptionKey = getEncryptionKey();
|
||||
}
|
||||
String server = KeycloakInstalled.console().readLine("Authentication server URL [http://localhost:8080/auth]: ").trim();
|
||||
String realm = KeycloakInstalled.console().readLine("Name of realm [master]: ").trim();
|
||||
String client = KeycloakInstalled.console().readLine("CLI client id [kcinit]: ").trim();
|
||||
String secret = KeycloakInstalled.console().readLine("CLI client secret [none]: ").trim();
|
||||
if (server.equals("")) {
|
||||
server = "http://localhost:8080/auth";
|
||||
}
|
||||
if (realm.equals("")) {
|
||||
realm = "master";
|
||||
}
|
||||
if (client.equals("")) {
|
||||
client = "kcinit";
|
||||
}
|
||||
File configDir = getTokenDirectory();
|
||||
configDir.mkdirs();
|
||||
|
||||
File configFile = getConfigFile();
|
||||
Map<String, String> props = new HashMap<>();
|
||||
props.put("server", server);
|
||||
props.put("realm", realm);
|
||||
props.put("client", client);
|
||||
props.put("secret", secret);
|
||||
|
||||
try {
|
||||
String json = JsonSerialization.writeValueAsString(props);
|
||||
writeFile(configFile, json);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
KeycloakInstalled.console().writer().println();
|
||||
KeycloakInstalled.console().writer().println("Installation complete!");
|
||||
KeycloakInstalled.console().writer().println();
|
||||
}
|
||||
|
||||
|
||||
public void printHelp() {
|
||||
KeycloakInstalled.console().writer().println("Commands:");
|
||||
KeycloakInstalled.console().writer().println(" login [-f] -f forces login");
|
||||
KeycloakInstalled.console().writer().println(" logout");
|
||||
KeycloakInstalled.console().writer().println(" token [client] - print access token of desired client. Defaults to default master client. Will print either 'error', 'not-allowed', or 'login-required' on error.");
|
||||
KeycloakInstalled.console().writer().println(" install - Install this utility. Will store in $HOME/.keycloak/kcinit unless " + KC_LOGIN_CONFIG_PATH + " env var is set");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
|
||||
public AdapterConfig getConfig() {
|
||||
File configFile = getConfigFile();
|
||||
if (!configFile.exists()) {
|
||||
KeycloakInstalled.console().writer().println("You have not configured kcinit. Please run 'kcinit install' to configure.");
|
||||
System.exit(1);
|
||||
return null;
|
||||
}
|
||||
|
||||
AdapterConfig config = new AdapterConfig();
|
||||
config.setAuthServerUrl(getConfigProperties().get("server"));
|
||||
config.setRealm(getConfigProperties().get("realm"));
|
||||
config.setResource(getConfigProperties().get("client"));
|
||||
config.setSslRequired("external");
|
||||
String secret = getConfigProperties().get("secret");
|
||||
if (secret != null && !secret.trim().equals("")) {
|
||||
Map<String, Object> creds = new HashMap<>();
|
||||
creds.put("secret", secret);
|
||||
config.setCredentials(creds);
|
||||
} else {
|
||||
config.setPublicClient(true);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
private Map<String, String> getConfigProperties() {
|
||||
if (this.config != null) return this.config;
|
||||
if (!getConfigFile().exists()) {
|
||||
KeycloakInstalled.console().writer().println();
|
||||
KeycloakInstalled.console().writer().println(("Config file does not exist. Run kcinit install to set it up."));
|
||||
System.exit(1);
|
||||
}
|
||||
String json = readFile(getConfigFile());
|
||||
try {
|
||||
Map map = JsonSerialization.readValue(json, Map.class);
|
||||
config = (Map<String, String>) map;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return this.config;
|
||||
}
|
||||
|
||||
public String readToken(String client) throws Exception {
|
||||
String json = getTokenResponse(client);
|
||||
if (json == null) return null;
|
||||
|
||||
|
||||
if (json != null) {
|
||||
try {
|
||||
AccessTokenResponse tokenResponse = JsonSerialization.readValue(json, AccessTokenResponse.class);
|
||||
if (Time.currentTime() < tokenResponse.getExpiresIn()) {
|
||||
return tokenResponse.getToken();
|
||||
}
|
||||
AdapterConfig config = getConfig();
|
||||
KeycloakInstalled installed = new KeycloakInstalled(KeycloakDeploymentBuilder.build(config));
|
||||
installed.refreshToken(tokenResponse.getRefreshToken());
|
||||
processResponse(installed, client);
|
||||
return tokenResponse.getToken();
|
||||
} catch (Exception e) {
|
||||
File tokenFile = getTokenFilePath(client);
|
||||
if (tokenFile.exists()) {
|
||||
tokenFile.delete();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
public String readRefreshToken(String client) throws Exception {
|
||||
String json = getTokenResponse(client);
|
||||
if (json == null) return null;
|
||||
|
||||
|
||||
if (json != null) {
|
||||
try {
|
||||
AccessTokenResponse tokenResponse = JsonSerialization.readValue(json, AccessTokenResponse.class);
|
||||
return tokenResponse.getRefreshToken();
|
||||
} catch (Exception e) {
|
||||
if (debug) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
File tokenFile = getTokenFilePath(client);
|
||||
if (tokenFile.exists()) {
|
||||
tokenFile.delete();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
|
||||
private String getTokenResponse(String client) {
|
||||
File tokenFile = getTokenFilePath(client);
|
||||
try {
|
||||
return readFile(tokenFile);
|
||||
} catch (Exception e) {
|
||||
if (debug) {
|
||||
System.err.println("Failed to read encrypted file");
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (tokenFile.exists()) tokenFile.delete();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void token() throws Exception {
|
||||
KeycloakInstalled.console().stderrOutput();
|
||||
|
||||
checkEnv();
|
||||
String masterClient = getMasterClient();
|
||||
String client = masterClient;
|
||||
if (args.length > 1) {
|
||||
client = args[1];
|
||||
}
|
||||
//System.err.println("readToken: " + client);
|
||||
String token = readToken(client);
|
||||
if (token != null) {
|
||||
System.out.print(token);
|
||||
return;
|
||||
}
|
||||
if (token == null && client.equals(masterClient)) {
|
||||
//System.err.println("not logged in, logging in.");
|
||||
doConsoleLogin();
|
||||
token = readToken(client);
|
||||
if (token != null) {
|
||||
System.out.print(token);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
String masterToken = readToken(masterClient);
|
||||
if (masterToken == null) {
|
||||
//System.err.println("not logged in, logging in.");
|
||||
doConsoleLogin();
|
||||
masterToken = readToken(masterClient);
|
||||
if (masterToken == null) {
|
||||
System.err.println("Login failed. Cannot retrieve token");
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
//System.err.println("exchange: " + client);
|
||||
Client httpClient = getHttpClient();
|
||||
|
||||
WebTarget exchangeUrl = httpClient.target(getServer())
|
||||
.path("/realms")
|
||||
.path(getRealm())
|
||||
.path("protocol/openid-connect/token");
|
||||
|
||||
Form form = new Form()
|
||||
.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE)
|
||||
.param(OAuth2Constants.CLIENT_ID, masterClient)
|
||||
.param(OAuth2Constants.SUBJECT_TOKEN, masterToken)
|
||||
.param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.ACCESS_TOKEN_TYPE)
|
||||
.param(OAuth2Constants.REQUESTED_TOKEN_TYPE, OAuth2Constants.REFRESH_TOKEN_TYPE)
|
||||
.param(OAuth2Constants.AUDIENCE, client);
|
||||
if (getMasterClientSecret() != null) {
|
||||
form.param(OAuth2Constants.CLIENT_SECRET, getMasterClientSecret());
|
||||
}
|
||||
Response response = exchangeUrl.request().post(Entity.form(
|
||||
form
|
||||
));
|
||||
|
||||
if (response.getStatus() == 401 || response.getStatus() == 403) {
|
||||
response.close();
|
||||
System.err.println("Not allowed to exchange for client token");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
if (response.getStatus() != 200) {
|
||||
if (response.getMediaType() != null && response.getMediaType().equals(MediaType.APPLICATION_JSON_TYPE)) {
|
||||
try {
|
||||
String json = response.readEntity(String.class);
|
||||
OAuth2ErrorRepresentation error = JsonSerialization.readValue(json, OAuth2ErrorRepresentation.class);
|
||||
System.err.println("Failed to exchange token: " + error.getError() + ". " + error.getErrorDescription());
|
||||
System.exit(1);
|
||||
} catch (Exception ignore) {
|
||||
ignore.printStackTrace();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
response.close();
|
||||
System.err.println("Unknown error exchanging for client token: " + response.getStatus());
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
String json = response.readEntity(String.class);
|
||||
response.close();
|
||||
AccessTokenResponse tokenResponse = JsonSerialization.readValue(json, AccessTokenResponse.class);
|
||||
if (tokenResponse.getToken() != null) {
|
||||
getTokenDirectory().mkdirs();
|
||||
tokenResponse.setExpiresIn(Time.currentTime() + tokenResponse.getExpiresIn());
|
||||
tokenResponse.setIdToken(null);
|
||||
json = JsonSerialization.writeValueAsString(tokenResponse);
|
||||
writeFile(getTokenFilePath(client), json);
|
||||
System.out.printf(tokenResponse.getToken());
|
||||
} else {
|
||||
System.err.println("Error processing token");
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
protected String getMasterClientSecret() {
|
||||
return getProperty("secret");
|
||||
}
|
||||
|
||||
protected String getServer() {
|
||||
return getProperty("server");
|
||||
}
|
||||
|
||||
protected String getRealm() {
|
||||
return getProperty("realm");
|
||||
}
|
||||
|
||||
public String getProperty(String name) {
|
||||
return getConfigProperties().get(name);
|
||||
}
|
||||
|
||||
protected boolean forceLogin() {
|
||||
return args.length > 0 && args[0].equals("-f");
|
||||
|
||||
}
|
||||
|
||||
public Client getHttpClient() {
|
||||
return new ResteasyClientBuilder().disableTrustManager().build();
|
||||
}
|
||||
|
||||
public void login() throws Exception {
|
||||
checkEnv();
|
||||
this.args = Arrays.copyOfRange(this.args, 1, this.args.length);
|
||||
for (String arg : args) {
|
||||
if (arg.equals("-f") || arg.equals("-force")) {
|
||||
forceLogin = true;
|
||||
this.args = Arrays.copyOfRange(this.args, 1, this.args.length);
|
||||
} else if (arg.equals("-browser") || arg.equals("-b")) {
|
||||
browserLogin = true;
|
||||
this.args = Arrays.copyOfRange(this.args, 1, this.args.length);
|
||||
} else {
|
||||
System.err.println("Illegal argument: " + arg);
|
||||
printHelp();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
String masterClient = getMasterClient();
|
||||
if (!forceLogin && readToken(masterClient) != null) {
|
||||
KeycloakInstalled.console().writer().println("Already logged in. `kcinit -f` to force relogin");
|
||||
return;
|
||||
}
|
||||
doConsoleLogin();
|
||||
KeycloakInstalled.console().writer().println("Login successful!");
|
||||
}
|
||||
|
||||
public void doConsoleLogin() throws Exception {
|
||||
String masterClient = getMasterClient();
|
||||
AdapterConfig config = getConfig();
|
||||
KeycloakInstalled installed = new KeycloakInstalled(KeycloakDeploymentBuilder.build(config));
|
||||
//System.err.println("calling loginCommandLine");
|
||||
if (!installed.loginCommandLine()) {
|
||||
System.exit(1);
|
||||
}
|
||||
processResponse(installed, masterClient);
|
||||
}
|
||||
|
||||
private String getMasterClient() {
|
||||
return getProperty("client");
|
||||
}
|
||||
|
||||
private void processResponse(KeycloakInstalled installed, String client) throws IOException {
|
||||
AccessTokenResponse tokenResponse = installed.getTokenResponse();
|
||||
tokenResponse.setExpiresIn(Time.currentTime() + tokenResponse.getExpiresIn());
|
||||
tokenResponse.setIdToken(null);
|
||||
String json = JsonSerialization.writeValueAsString(tokenResponse);
|
||||
getTokenDirectory().mkdirs();
|
||||
writeFile(getTokenFilePath(client), json);
|
||||
}
|
||||
|
||||
public void logout() throws Exception {
|
||||
String token = readRefreshToken(getMasterClient());
|
||||
if (token != null) {
|
||||
try {
|
||||
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getConfig());
|
||||
ServerRequest.invokeLogout(deployment, token);
|
||||
} catch (Exception e) {
|
||||
if (debug) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if (getTokenDirectory().exists()) {
|
||||
for (File fp : getTokenDirectory().listFiles()) fp.delete();
|
||||
}
|
||||
}
|
||||
public void uninstall() throws Exception {
|
||||
File configFile = getConfigFile();
|
||||
if (configFile.exists()) configFile.delete();
|
||||
if (getTokenDirectory().exists()) {
|
||||
for (File fp : getTokenDirectory().listFiles()) fp.delete();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
CLI Single Sign On
|
||||
===================================
|
||||
|
||||
This java-based utility is meant for providing Keycloak integration to
|
||||
command line applications that are either written in Java or another language. The
|
||||
idea is that the Java app provided by this utility performs a login for a specific
|
||||
client, parses responses, and exports an access token as an environment variable
|
||||
that can be used by the command line utility you are accessing.
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<!--
|
||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
~ and other contributors as indicated by the @author tags.
|
||||
~
|
||||
~ Licensed 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.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>16.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>kcinit</artifactId>
|
||||
<name>Keycloak CLI SSO Framework</name>
|
||||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-installed-adapter</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<configuration>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>org.keycloak.adapters.KcinitMain</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/*.SF</exclude>
|
||||
<exclude>META-INF/*.DSA</exclude>
|
||||
<exclude>META-INF/*.RSA</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -1,26 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
case "`uname`" in
|
||||
CYGWIN*)
|
||||
CFILE = `cygpath "$0"`
|
||||
RESOLVED_NAME=`readlink -f "$CFILE"`
|
||||
;;
|
||||
Darwin*)
|
||||
RESOLVED_NAME=`readlink "$0"`
|
||||
;;
|
||||
FreeBSD)
|
||||
RESOLVED_NAME=`readlink -f "$0"`
|
||||
;;
|
||||
Linux)
|
||||
RESOLVED_NAME=`readlink -f "$0"`
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "x$RESOLVED_NAME" = "x" ]; then
|
||||
RESOLVED_NAME="$0"
|
||||
fi
|
||||
|
||||
SCRIPTPATH=`dirname "$RESOLVED_NAME"`
|
||||
JAR=$SCRIPTPATH/kcinit-${project.version}.jar
|
||||
|
||||
java -jar $JAR $@
|
|
@ -1,8 +0,0 @@
|
|||
@echo off
|
||||
|
||||
if "%OS%" == "Windows_NT" (
|
||||
set "DIRNAME=%~dp0%"
|
||||
) else (
|
||||
set DIRNAME=.\
|
||||
)
|
||||
java -jar %DIRNAME%\kcinit-${project.version}.jar %*
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed 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.keycloak.adapters;
|
||||
|
||||
import org.keycloak.adapters.installed.KcinitDriver;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KcinitMain extends KcinitDriver {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
new KcinitMain().mainCmd(args);
|
||||
}
|
||||
}
|
|
@ -34,7 +34,6 @@
|
|||
<module>adapter-core</module>
|
||||
<module>installed</module>
|
||||
<module>fuse7</module>
|
||||
<module>kcinit</module>
|
||||
<module>jaxrs-oauth-client</module>
|
||||
<module>jetty</module>
|
||||
<module>js</module>
|
||||
|
|
|
@ -22,12 +22,5 @@ fi
|
|||
DIRNAME=`dirname "$RESOLVED_NAME"`
|
||||
|
||||
|
||||
# Uncomment out these lines if you are integrating with `kcinit`
|
||||
#if [ "$1" = "config" ]; then
|
||||
# java $KC_OPTS -cp $DIRNAME/client/keycloak-admin-cli-${project.version}.jar org.keycloak.client.admin.cli.KcAdmMain "$@"
|
||||
#else
|
||||
# java $KC_OPTS -cp $DIRNAME/client/keycloak-admin-cli-${project.version}.jar org.keycloak.client.admin.cli.KcAdmMain "$@" --noconfig --token $(kcinit token admin-cli) --server $(kcinit show server)
|
||||
#fi
|
||||
# Remove the next line if you have enabled kcinit
|
||||
java $KC_OPTS -cp $DIRNAME/client/keycloak-admin-cli-${project.version}.jar org.keycloak.client.admin.cli.KcAdmMain "$@"
|
||||
|
||||
|
|
11
pom.xml
11
pom.xml
|
@ -1646,17 +1646,6 @@
|
|||
<version>${project.version}</version>
|
||||
<type>zip</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>kcinit</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>kcinit-dist</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>zip</type>
|
||||
</dependency>
|
||||
<!-- webauthn support -->
|
||||
<dependency>
|
||||
<groupId>com.webauthn4j</groupId>
|
||||
|
|
|
@ -124,11 +124,6 @@ public class OIDCLoginProtocolService {
|
|||
return uriBuilder.path(OIDCLoginProtocolService.class, "registrations");
|
||||
}
|
||||
|
||||
public static UriBuilder delegatedUrl(UriInfo uriInfo) {
|
||||
UriBuilder uriBuilder = tokenServiceBaseUrl(uriInfo);
|
||||
return uriBuilder.path(OIDCLoginProtocolService.class, "kcinitBrowserLoginComplete");
|
||||
}
|
||||
|
||||
public static UriBuilder tokenUrl(UriBuilder baseUriBuilder) {
|
||||
UriBuilder uriBuilder = tokenServiceBaseUrl(baseUriBuilder);
|
||||
return uriBuilder.path(OIDCLoginProtocolService.class, "token");
|
||||
|
@ -289,33 +284,6 @@ public class OIDCLoginProtocolService {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For KeycloakInstalled and kcinit login where command line login is delegated to a browser.
|
||||
* This clears login cookies and outputs login success or failure messages.
|
||||
*
|
||||
* @param error
|
||||
* @return
|
||||
*/
|
||||
@GET
|
||||
@Path("delegated")
|
||||
public Response kcinitBrowserLoginComplete(@QueryParam("error") boolean error) {
|
||||
AuthenticationManager.expireIdentityCookie(realm, session.getContext().getUri(), clientConnection);
|
||||
AuthenticationManager.expireRememberMeCookie(realm, session.getContext().getUri(), clientConnection);
|
||||
if (error) {
|
||||
LoginFormsProvider forms = session.getProvider(LoginFormsProvider.class);
|
||||
return forms
|
||||
.setAttribute("messageHeader", forms.getMessage(Messages.DELEGATION_FAILED_HEADER))
|
||||
.setAttribute(Constants.SKIP_LINK, true).setError(Messages.DELEGATION_FAILED).createInfoPage();
|
||||
|
||||
} else {
|
||||
LoginFormsProvider forms = session.getProvider(LoginFormsProvider.class);
|
||||
return forms
|
||||
.setAttribute("messageHeader", forms.getMessage(Messages.DELEGATION_COMPLETE_HEADER))
|
||||
.setAttribute(Constants.SKIP_LINK, true)
|
||||
.setSuccess(Messages.DELEGATION_COMPLETE).createInfoPage();
|
||||
}
|
||||
}
|
||||
|
||||
@Path("ext/{extension}")
|
||||
public Object resolveExtension(@PathParam("extension") String extension) {
|
||||
OIDCExtProvider provider = session.getProvider(OIDCExtProvider.class, extension);
|
||||
|
|
|
@ -288,43 +288,6 @@
|
|||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.igormaznitsa</groupId>
|
||||
<artifactId>mvn-golang-wrapper</artifactId>
|
||||
<version>2.3.4</version>
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<goVersion>1.9.2</goVersion>
|
||||
<useMavenProxy>true</useMavenProxy>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>get-mousetrap</id>
|
||||
<goals>
|
||||
<goal>get</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<packages>
|
||||
<package>github.com/inconshreveable/mousetrap</package>
|
||||
</packages>
|
||||
<goPath>${project.build.directory}/gopath</goPath>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>get-kcinit</id>
|
||||
<goals>
|
||||
<goal>get</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<packages>
|
||||
<package>github.com/keycloak/kcinit</package>
|
||||
</packages>
|
||||
<goPath>${project.build.directory}/gopath</goPath>
|
||||
<tag>0.5</tag>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>io.fabric8</groupId>
|
||||
<artifactId>docker-maven-plugin</artifactId>
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
package org.keycloak.testsuite.cli;
|
||||
|
||||
import org.keycloak.testsuite.cli.exec.AbstractExec;
|
||||
import org.keycloak.testsuite.cli.exec.AbstractExecBuilder;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
public class KcinitExec extends AbstractExec {
|
||||
|
||||
public static final String WORK_DIR = System.getProperty("user.dir") + "/target";
|
||||
|
||||
public static final String CMD = OS_ARCH.isWindows() ? "kcinit" : "kcinit";
|
||||
|
||||
private KcinitExec(String workDir, String argsLine, InputStream stdin) {
|
||||
this(workDir, argsLine, null, stdin);
|
||||
}
|
||||
|
||||
private KcinitExec(String workDir, String argsLine, String env, InputStream stdin) {
|
||||
super(workDir, argsLine, env, stdin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCmd() {
|
||||
return "./" + CMD;
|
||||
}
|
||||
|
||||
public static KcinitExec.Builder newBuilder() {
|
||||
return (KcinitExec.Builder) new KcinitExec.Builder().workDir(WORK_DIR);
|
||||
}
|
||||
|
||||
public static KcinitExec execute(String args) {
|
||||
return newBuilder()
|
||||
.argsLine(args)
|
||||
.execute();
|
||||
}
|
||||
|
||||
public static class Builder extends AbstractExecBuilder<KcinitExec> {
|
||||
|
||||
@Override
|
||||
public KcinitExec execute() {
|
||||
KcinitExec exe = new KcinitExec(workDir, argsLine, env, stdin);
|
||||
exe.dumpStreams = dumpStreams;
|
||||
exe.execute();
|
||||
return exe;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KcinitExec executeAsync() {
|
||||
KcinitExec exe = new KcinitExec(workDir, argsLine, env, stdin);
|
||||
exe.dumpStreams = dumpStreams;
|
||||
exe.executeAsync();
|
||||
return exe;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1298,12 +1298,12 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest {
|
|||
private String checkTokenExchange(boolean shouldPass) throws Exception {
|
||||
testingClient.server().run(FineGrainAdminUnitTest::setupTokenExchange);
|
||||
oauth.realm("master");
|
||||
oauth.clientId("kcinit");
|
||||
oauth.clientId("tokenexclient");
|
||||
String exchanged = null;
|
||||
String token = oauth.doGrantAccessTokenRequest("password", "admin", "admin").getAccessToken();
|
||||
Assert.assertNotNull(token);
|
||||
try {
|
||||
exchanged = oauth.doTokenExchange("master", token, "admin-cli", "kcinit", "password").getAccessToken();
|
||||
exchanged = oauth.doTokenExchange("master", token, "admin-cli", "tokenexclient", "password").getAccessToken();
|
||||
} catch (AssertionError e) {
|
||||
log.info("Error message is expected from oauth: " + e.getMessage());
|
||||
}
|
||||
|
@ -1316,17 +1316,17 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest {
|
|||
|
||||
private static void setupTokenExchange(KeycloakSession session) {
|
||||
RealmModel realm = session.realms().getRealmByName("master");
|
||||
ClientModel client = session.clients().getClientByClientId(realm, "kcinit");
|
||||
ClientModel client = session.clients().getClientByClientId(realm, "tokenexclient");
|
||||
if (client != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ClientModel kcinit = realm.addClient("kcinit");
|
||||
kcinit.setEnabled(true);
|
||||
kcinit.addRedirectUri("http://localhost:*");
|
||||
kcinit.setPublicClient(false);
|
||||
kcinit.setSecret("password");
|
||||
kcinit.setDirectAccessGrantsEnabled(true);
|
||||
ClientModel tokenexclient = realm.addClient("tokenexclient");
|
||||
tokenexclient.setEnabled(true);
|
||||
tokenexclient.addRedirectUri("http://localhost:*");
|
||||
tokenexclient.setPublicClient(false);
|
||||
tokenexclient.setSecret("password");
|
||||
tokenexclient.setDirectAccessGrantsEnabled(true);
|
||||
|
||||
// permission for client to client exchange to "target" client
|
||||
ClientModel adminCli = realm.getClientByClientId(ConfigUtil.DEFAULT_CLIENT);
|
||||
|
@ -1334,7 +1334,7 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest {
|
|||
management.clients().setPermissionsEnabled(adminCli, true);
|
||||
ClientPolicyRepresentation clientRep = new ClientPolicyRepresentation();
|
||||
clientRep.setName("to");
|
||||
clientRep.addClient(kcinit.getId());
|
||||
clientRep.addClient(tokenexclient.getId());
|
||||
ResourceServer server = management.realmResourceServer();
|
||||
Policy clientPolicy = management.authz().getStoreFactory().getPolicyStore().create(clientRep, server);
|
||||
management.clients().exchangeToPermission(adminCli).addAssociatedPolicy(clientPolicy);
|
||||
|
|
|
@ -1,698 +0,0 @@
|
|||
/*
|
||||
* Copyright 2017 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed 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.keycloak.testsuite.cli;
|
||||
|
||||
import java.io.File;
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.authentication.requiredactions.TermsAndConditions;
|
||||
import org.keycloak.authorization.model.Policy;
|
||||
import org.keycloak.authorization.model.ResourceServer;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.AuthenticationFlowBindings;
|
||||
import org.keycloak.models.AuthenticationFlowModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.PasswordPolicy;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RequiredActionProviderModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.credential.OTPCredentialModel;
|
||||
import org.keycloak.models.utils.DefaultAuthenticationFlows;
|
||||
import org.keycloak.models.utils.TimeBasedOTP;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation;
|
||||
import org.keycloak.services.resources.admin.AuthenticationManagementResource;
|
||||
import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement;
|
||||
import org.keycloak.services.resources.admin.permissions.AdminPermissions;
|
||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.ProfileAssume;
|
||||
import org.keycloak.testsuite.actions.DummyRequiredActionFactory;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
|
||||
import org.keycloak.testsuite.authentication.PushButtonAuthenticatorFactory;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
import org.keycloak.testsuite.util.GreenMailRule;
|
||||
import org.keycloak.testsuite.util.MailUtils;
|
||||
import org.keycloak.utils.TotpUtils;
|
||||
import org.openqa.selenium.By;
|
||||
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.Assume;
|
||||
import org.junit.BeforeClass;
|
||||
|
||||
import static org.keycloak.common.Profile.Feature.AUTHORIZATION;
|
||||
|
||||
/**
|
||||
* Test that clients can override auth flows
|
||||
*
|
||||
* @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
|
||||
*/
|
||||
@AuthServerContainerExclude(AuthServer.REMOTE)
|
||||
public class KcinitTest extends AbstractTestRealmKeycloakTest {
|
||||
|
||||
public static final String KCINIT_CLIENT = "kcinit";
|
||||
public static final String APP = "app";
|
||||
public static final String UNAUTHORIZED_APP = "unauthorized_app";
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(this);
|
||||
|
||||
@Page
|
||||
protected LoginPage loginPage;
|
||||
|
||||
@BeforeClass
|
||||
public static void enabled() {
|
||||
ProfileAssume.assumeFeatureEnabled(AUTHORIZATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void kcinitAvailable() {
|
||||
Assume.assumeTrue(new File(KcinitExec.WORK_DIR + File.separator + KcinitExec.CMD).exists());
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setupFlows() {
|
||||
RequiredActionProviderRepresentation rep = adminClient.realm("test").flows().getRequiredAction("terms_and_conditions");
|
||||
rep.setEnabled(true);
|
||||
adminClient.realm("test").flows().updateRequiredAction("terms_and_conditions", rep);
|
||||
|
||||
testingClient.server().run(session -> {
|
||||
RealmModel realm = session.realms().getRealmByName("test");
|
||||
|
||||
ClientModel client = session.clients().getClientByClientId(realm, "kcinit");
|
||||
if (client != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ClientModel kcinit = realm.addClient(KCINIT_CLIENT);
|
||||
kcinit.setEnabled(true);
|
||||
kcinit.addRedirectUri("*");
|
||||
kcinit.setPublicClient(true);
|
||||
kcinit.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||
kcinit.removeRole(realm.getRole(OAuth2Constants.OFFLINE_ACCESS));
|
||||
|
||||
ClientModel app = realm.addClient(APP);
|
||||
app.setSecret("password");
|
||||
app.setEnabled(true);
|
||||
app.setPublicClient(false);
|
||||
|
||||
ClientModel unauthorizedApp = realm.addClient(UNAUTHORIZED_APP);
|
||||
unauthorizedApp.setSecret("password");
|
||||
unauthorizedApp.setEnabled(true);
|
||||
unauthorizedApp.setPublicClient(false);
|
||||
|
||||
// permission for client to client exchange to "target" client
|
||||
AdminPermissionManagement management = AdminPermissions.management(session, realm);
|
||||
management.clients().setPermissionsEnabled(app, true);
|
||||
ClientPolicyRepresentation clientRep = new ClientPolicyRepresentation();
|
||||
clientRep.setName("to");
|
||||
clientRep.addClient(kcinit.getId());
|
||||
ResourceServer server = management.realmResourceServer();
|
||||
Policy clientPolicy = management.authz().getStoreFactory().getPolicyStore().create(clientRep, server);
|
||||
management.clients().exchangeToPermission(app).addAssociatedPolicy(clientPolicy);
|
||||
PasswordPolicy policy = realm.getPasswordPolicy();
|
||||
policy = PasswordPolicy.parse(session, "hashIterations(1)");
|
||||
realm.setPasswordPolicy(policy);
|
||||
|
||||
UserModel user = session.users().addUser(realm, "bburke");
|
||||
session.userCredentialManager().updateCredential(realm, user, UserCredentialModel.password("password"));
|
||||
user.setEnabled(true);
|
||||
user.setEmail("p@p.com");
|
||||
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||
user.addRequiredAction(UserModel.RequiredAction.CONFIGURE_TOTP);
|
||||
user.addRequiredAction(UserModel.RequiredAction.VERIFY_EMAIL);
|
||||
user.addRequiredAction(TermsAndConditions.PROVIDER_ID);
|
||||
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PROFILE);
|
||||
|
||||
user = session.users().addUser(realm, "wburke");
|
||||
session.userCredentialManager().updateCredential(realm, user, UserCredentialModel.password("password"));
|
||||
user.setEnabled(true);
|
||||
user = session.users().addUser(realm, "tbrady");
|
||||
session.userCredentialManager().updateCredential(realm, user, UserCredentialModel.password("password"));
|
||||
user.setEnabled(true);
|
||||
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||
|
||||
// Parent flow
|
||||
AuthenticationFlowModel browser = new AuthenticationFlowModel();
|
||||
browser.setAlias("no-console-flow");
|
||||
browser.setDescription("browser based authentication");
|
||||
browser.setProviderId("basic-flow");
|
||||
browser.setTopLevel(true);
|
||||
browser.setBuiltIn(true);
|
||||
browser = realm.addAuthenticationFlow(browser);
|
||||
|
||||
AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
|
||||
execution.setParentFlow(browser.getId());
|
||||
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
|
||||
execution.setPriority(20);
|
||||
execution.setAuthenticator(PushButtonAuthenticatorFactory.PROVIDER_ID);
|
||||
realm.addAuthenticatorExecution(execution);
|
||||
|
||||
AuthenticationFlowModel browserBuiltin = realm.getFlowByAlias(DefaultAuthenticationFlows.BROWSER_FLOW);
|
||||
AuthenticationFlowModel copy = AuthenticationManagementResource.copyFlow(realm, browserBuiltin, "copy-browser");
|
||||
copy.setTopLevel(false);
|
||||
realm.updateAuthenticationFlow(copy);
|
||||
execution = new AuthenticationExecutionModel();
|
||||
execution.setParentFlow(browser.getId());
|
||||
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
|
||||
execution.setFlowId(copy.getId());
|
||||
execution.setPriority(30);
|
||||
execution.setAuthenticatorFlow(true);
|
||||
realm.addAuthenticatorExecution(execution);
|
||||
|
||||
RequiredActionProviderModel action = new RequiredActionProviderModel();
|
||||
action.setAlias("dummy");
|
||||
action.setEnabled(true);
|
||||
action.setProviderId(DummyRequiredActionFactory.PROVIDER_ID);
|
||||
action.setName("dummy");
|
||||
action = realm.addRequiredActionProvider(action);
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
//@Test
|
||||
public void testDemo() throws Exception {
|
||||
testingClient.server().run(session -> {
|
||||
RealmModel realm = session.realms().getRealmByName("test");
|
||||
Map<String, String> smtp = new HashMap<>();
|
||||
smtp.put("host", "smtp.gmail.com");
|
||||
smtp.put("port", "465");
|
||||
smtp.put("fromDisplayName", "Keycloak SSO");
|
||||
smtp.put("from", "****");
|
||||
smtp.put("replyToDisplayName", "Keycloak no-reply");
|
||||
smtp.put("replyTo", "reply-to@keycloak.org");
|
||||
smtp.put("ssl", "true");
|
||||
smtp.put("auth", "true");
|
||||
smtp.put("user", "*****");
|
||||
smtp.put("password", "****");
|
||||
realm.setSmtpConfig(smtp);
|
||||
|
||||
});
|
||||
|
||||
Thread.sleep(100000000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBrowserContinueAuthenticator() throws Exception {
|
||||
// test that we can continue in the middle of a console login that doesn't support console display mode
|
||||
testingClient.server().run(session -> {
|
||||
RealmModel realm = session.realms().getRealmByName("test");
|
||||
ClientModel kcinit = realm.getClientByClientId(KCINIT_CLIENT);
|
||||
AuthenticationFlowModel flow = realm.getFlowByAlias("no-console-flow");
|
||||
kcinit.setAuthenticationFlowBindingOverride(AuthenticationFlowBindings.BROWSER_BINDING, flow.getId());
|
||||
|
||||
|
||||
});
|
||||
//Thread.sleep(100000000);
|
||||
|
||||
try {
|
||||
|
||||
testInstall();
|
||||
|
||||
KcinitExec exe = KcinitExec.newBuilder()
|
||||
.argsLine("login -f --fake-browser") // --fake-browser is a hidden command so that this test can execute
|
||||
.executeAsync();
|
||||
exe.waitForStderr("Open browser and continue login? [y/n]");
|
||||
exe.sendLine("y");
|
||||
exe.waitForStdout("http");
|
||||
|
||||
// the --fake-browser skips launching a browser and outputs url to stdout
|
||||
String redirect = exe.stdoutString().trim();
|
||||
|
||||
//System.out.println("********************************");
|
||||
//System.out.println("Redirect: " + redirect);
|
||||
|
||||
//redirect.replace("Browser required to complete login", "");
|
||||
|
||||
driver.navigate().to(redirect.trim());
|
||||
|
||||
Assert.assertEquals("PushTheButton", driver.getTitle());
|
||||
|
||||
// Push the button. I am redirected to username+password form
|
||||
driver.findElement(By.name("submit1")).click();
|
||||
//System.out.println("-----");
|
||||
//System.out.println(driver.getPageSource());
|
||||
|
||||
//System.out.println(driver.getTitle());
|
||||
|
||||
|
||||
|
||||
loginPage.assertCurrent();
|
||||
|
||||
// Fill username+password. I am successfully authenticated
|
||||
try {
|
||||
oauth.fillLoginForm("wburke", "password");
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
String current = driver.getCurrentUrl();
|
||||
|
||||
exe.waitForStderr("Login successful");
|
||||
exe.waitCompletion();
|
||||
Assert.assertEquals(0, exe.exitCode());
|
||||
Assert.assertTrue(driver.getPageSource().contains("Login Successful"));
|
||||
} finally {
|
||||
|
||||
testingClient.server().run(session -> {
|
||||
RealmModel realm = session.realms().getRealmByName("test");
|
||||
ClientModel kcinit = realm.getClientByClientId(KCINIT_CLIENT);
|
||||
kcinit.removeAuthenticationFlowBindingOverride(AuthenticationFlowBindings.BROWSER_BINDING);
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBrowserContinueRequiredAction() throws Exception {
|
||||
testingClient.server().run(session -> {
|
||||
RealmModel realm = session.realms().getRealmByName("test");
|
||||
UserModel user = session.users().getUserByUsername(realm, "wburke");
|
||||
user.addRequiredAction("dummy");
|
||||
});
|
||||
testInstall();
|
||||
// login
|
||||
//System.out.println("login....");
|
||||
KcinitExec exe = KcinitExec.newBuilder()
|
||||
.argsLine("login -f --fake-browser")
|
||||
.executeAsync();
|
||||
//System.out.println(exe.stderrString());
|
||||
exe.waitForStderr("Username:");
|
||||
exe.sendLine("wburke");
|
||||
//System.out.println(exe.stderrString());
|
||||
exe.waitForStderr("Password:");
|
||||
exe.sendLine("password");
|
||||
|
||||
exe.waitForStderr("Open browser and continue login? [y/n]");
|
||||
exe.sendLine("y");
|
||||
exe.waitForStdout("http");
|
||||
|
||||
// the --fake-browser skips launching a browser and outputs url to stdout
|
||||
String redirect = exe.stdoutString().trim();
|
||||
|
||||
driver.navigate().to(redirect.trim());
|
||||
|
||||
|
||||
//System.out.println(exe.stderrString());
|
||||
exe.waitForStderr("Login successful");
|
||||
exe.waitCompletion();
|
||||
Assert.assertEquals(0, exe.exitCode());
|
||||
Assert.assertTrue(driver.getPageSource().contains("Login Successful"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testBadCommand() throws Exception {
|
||||
KcinitExec exe = KcinitExec.execute("covfefe");
|
||||
Assert.assertEquals(1, exe.exitCode());
|
||||
Assert.assertEquals("stderr first line", "Error: unknown command \"covfefe\" for \"kcinit\"", exe.stderrLines().get(0));
|
||||
}
|
||||
|
||||
//@Test
|
||||
public void testInstall() throws Exception {
|
||||
KcinitExec exe = KcinitExec.execute("uninstall");
|
||||
Assert.assertEquals(0, exe.exitCode());
|
||||
|
||||
exe = KcinitExec.newBuilder()
|
||||
.argsLine("install")
|
||||
.executeAsync();
|
||||
//System.out.println(exe.stderrString());
|
||||
//exe.waitForStderr("(y/n):");
|
||||
//exe.sendLine("n");
|
||||
exe.waitForStderr("Authentication server URL [http://localhost:8080/auth]:");
|
||||
exe.sendLine(oauth.AUTH_SERVER_ROOT);
|
||||
//System.out.println(exe.stderrString());
|
||||
exe.waitForStderr("Name of realm [master]:");
|
||||
exe.sendLine("test");
|
||||
//System.out.println(exe.stderrString());
|
||||
exe.waitForStderr("client id [kcinit]:");
|
||||
exe.sendLine("");
|
||||
//System.out.println(exe.stderrString());
|
||||
exe.waitForStderr("secret [none]:");
|
||||
exe.sendLine("");
|
||||
//System.out.println(exe.stderrString());
|
||||
exe.waitCompletion();
|
||||
Assert.assertEquals(0, exe.exitCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOffline() throws Exception {
|
||||
testInstall();
|
||||
// login
|
||||
//System.out.println("login....");
|
||||
KcinitExec exe = KcinitExec.newBuilder()
|
||||
.argsLine("login --offline")
|
||||
.executeAsync();
|
||||
//System.out.println(exe.stderrString());
|
||||
exe.waitForStderr("Username:");
|
||||
exe.sendLine("wburke");
|
||||
//System.out.println(exe.stderrString());
|
||||
exe.waitForStderr("Password:");
|
||||
exe.sendLine("password");
|
||||
//System.out.println(exe.stderrString());
|
||||
exe.waitForStderr("Offline tokens not allowed for the user or client");
|
||||
exe.waitCompletion();
|
||||
Assert.assertEquals(1, exe.exitCode());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testBasic() throws Exception {
|
||||
testInstall();
|
||||
// login
|
||||
//System.out.println("login....");
|
||||
KcinitExec exe = KcinitExec.newBuilder()
|
||||
.argsLine("login")
|
||||
.executeAsync();
|
||||
//System.out.println(exe.stderrString());
|
||||
exe.waitForStderr("Username:");
|
||||
exe.sendLine("wburke");
|
||||
//System.out.println(exe.stderrString());
|
||||
exe.waitForStderr("Password:");
|
||||
exe.sendLine("password");
|
||||
//System.out.println(exe.stderrString());
|
||||
exe.waitForStderr("Login successful");
|
||||
exe.waitCompletion();
|
||||
Assert.assertEquals(0, exe.exitCode());
|
||||
Assert.assertEquals(0, exe.stdoutLines().size());
|
||||
|
||||
if (Profile.isFeatureEnabled(Profile.Feature.TOKEN_EXCHANGE)) {
|
||||
exe = KcinitExec.execute("token");
|
||||
Assert.assertEquals(0, exe.exitCode());
|
||||
Assert.assertEquals(1, exe.stdoutLines().size());
|
||||
String token = exe.stdoutLines().get(0).trim();
|
||||
//System.out.println("token: " + token);
|
||||
|
||||
exe = KcinitExec.execute("token app");
|
||||
Assert.assertEquals(0, exe.exitCode());
|
||||
Assert.assertEquals(1, exe.stdoutLines().size());
|
||||
String appToken = exe.stdoutLines().get(0).trim();
|
||||
Assert.assertFalse(appToken.equals(token));
|
||||
//System.out.println("token: " + token);
|
||||
|
||||
|
||||
exe = KcinitExec.execute("token badapp");
|
||||
Assert.assertEquals(1, exe.exitCode());
|
||||
Assert.assertEquals(0, exe.stdoutLines().size());
|
||||
Assert.assertEquals(1, exe.stderrLines().size());
|
||||
Assert.assertTrue(exe.stderrLines().get(0), exe.stderrLines().get(0).contains("failed to exchange token: invalid_client Audience not found"));
|
||||
}
|
||||
|
||||
exe = KcinitExec.execute("logout");
|
||||
Assert.assertEquals(0, exe.exitCode());
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTerms() throws Exception {
|
||||
testingClient.server().run(session -> {
|
||||
RealmModel realm = session.realms().getRealmByName("test");
|
||||
UserModel user = session.users().getUserByUsername(realm, "wburke");
|
||||
user.addRequiredAction(TermsAndConditions.PROVIDER_ID);
|
||||
});
|
||||
|
||||
testInstall();
|
||||
|
||||
KcinitExec exe = KcinitExec.newBuilder()
|
||||
.argsLine("login")
|
||||
.executeAsync();
|
||||
exe.waitForStderr("Username:");
|
||||
exe.sendLine("wburke");
|
||||
exe.waitForStderr("Password:");
|
||||
exe.sendLine("password");
|
||||
exe.waitForStderr("Accept Terms? [y/n]:");
|
||||
exe.sendLine("y");
|
||||
exe.waitForStderr("Login successful");
|
||||
exe.waitCompletion();
|
||||
Assert.assertEquals(0, exe.exitCode());
|
||||
Assert.assertEquals(0, exe.stdoutLines().size());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testUpdateProfile() throws Exception {
|
||||
// expects that updateProfile is a passthrough
|
||||
testingClient.server().run(session -> {
|
||||
RealmModel realm = session.realms().getRealmByName("test");
|
||||
UserModel user = session.users().getUserByUsername(realm, "wburke");
|
||||
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PROFILE);
|
||||
});
|
||||
|
||||
try {
|
||||
testInstall();
|
||||
|
||||
//Thread.sleep(100000000);
|
||||
|
||||
KcinitExec exe = KcinitExec.newBuilder()
|
||||
.argsLine("login")
|
||||
.executeAsync();
|
||||
try {
|
||||
exe.waitForStderr("Username:");
|
||||
exe.sendLine("wburke");
|
||||
exe.waitForStderr("Password:");
|
||||
exe.sendLine("password");
|
||||
|
||||
exe.waitForStderr("Login successful");
|
||||
exe.waitCompletion();
|
||||
Assert.assertEquals(0, exe.exitCode());
|
||||
Assert.assertEquals(0, exe.stdoutLines().size());
|
||||
} catch (Exception ex) {
|
||||
System.out.println(exe.stderrString());
|
||||
throw ex;
|
||||
}
|
||||
} finally {
|
||||
|
||||
testingClient.server().run(session -> {
|
||||
RealmModel realm = session.realms().getRealmByName("test");
|
||||
UserModel user = session.users().getUserByUsername(realm, "wburke");
|
||||
user.removeRequiredAction(UserModel.RequiredAction.UPDATE_PROFILE);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testUpdatePassword() throws Exception {
|
||||
testingClient.server().run(session -> {
|
||||
RealmModel realm = session.realms().getRealmByName("test");
|
||||
UserModel user = session.users().getUserByUsername(realm, "wburke");
|
||||
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||
});
|
||||
|
||||
try {
|
||||
testInstall();
|
||||
|
||||
KcinitExec exe = KcinitExec.newBuilder()
|
||||
.argsLine("login")
|
||||
.executeAsync();
|
||||
exe.waitForStderr("Username:");
|
||||
exe.sendLine("wburke");
|
||||
exe.waitForStderr("Password:");
|
||||
exe.sendLine("password");
|
||||
exe.waitForStderr("New Password:");
|
||||
exe.sendLine("pw");
|
||||
exe.waitForStderr("Confirm Password:");
|
||||
exe.sendLine("pw");
|
||||
exe.waitForStderr("Login successful");
|
||||
exe.waitCompletion();
|
||||
Assert.assertEquals(0, exe.exitCode());
|
||||
Assert.assertEquals(0, exe.stdoutLines().size());
|
||||
|
||||
exe = KcinitExec.newBuilder()
|
||||
.argsLine("login -f")
|
||||
.executeAsync();
|
||||
exe.waitForStderr("Username:");
|
||||
exe.sendLine("wburke");
|
||||
exe.waitForStderr("Password:");
|
||||
exe.sendLine("pw");
|
||||
exe.waitForStderr("Login successful");
|
||||
exe.waitCompletion();
|
||||
Assert.assertEquals(0, exe.exitCode());
|
||||
Assert.assertEquals(0, exe.stdoutLines().size());
|
||||
|
||||
exe = KcinitExec.execute("logout");
|
||||
Assert.assertEquals(0, exe.exitCode());
|
||||
} finally {
|
||||
|
||||
testingClient.server().run(session -> {
|
||||
RealmModel realm = session.realms().getRealmByName("test");
|
||||
UserModel user = session.users().getUserByUsername(realm, "wburke");
|
||||
session.userCredentialManager().updateCredential(realm, user, UserCredentialModel.password("password"));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected TimeBasedOTP totp = new TimeBasedOTP();
|
||||
|
||||
|
||||
@Test
|
||||
public void testConfigureTOTP() throws Exception {
|
||||
testingClient.server().run(session -> {
|
||||
RealmModel realm = session.realms().getRealmByName("test");
|
||||
UserModel user = session.users().getUserByUsername(realm, "wburke");
|
||||
user.addRequiredAction(UserModel.RequiredAction.CONFIGURE_TOTP);
|
||||
});
|
||||
|
||||
try {
|
||||
|
||||
testInstall();
|
||||
|
||||
KcinitExec exe = KcinitExec.newBuilder()
|
||||
.argsLine("login")
|
||||
.executeAsync();
|
||||
exe.waitForStderr("Username:");
|
||||
exe.sendLine("wburke");
|
||||
exe.waitForStderr("Password:");
|
||||
exe.sendLine("password");
|
||||
exe.waitForStderr("One Time Password:");
|
||||
|
||||
Pattern p = Pattern.compile("Open the application and enter the key:\\s+(.+)\\s+Use the following configuration values");
|
||||
//Pattern p = Pattern.compile("Open the application and enter the key:");
|
||||
|
||||
String stderr = exe.stderrString();
|
||||
//System.out.println("***************");
|
||||
//System.out.println(stderr);
|
||||
//System.out.println("***************");
|
||||
Matcher m = p.matcher(stderr);
|
||||
Assert.assertTrue(m.find());
|
||||
String otpSecret = m.group(1).trim();
|
||||
|
||||
//System.out.println("***************");
|
||||
//System.out.println(otpSecret);
|
||||
//System.out.println("***************");
|
||||
|
||||
otpSecret = TotpUtils.decode(otpSecret);
|
||||
String code = totp.generateTOTP(otpSecret);
|
||||
//System.out.println("***************");
|
||||
//System.out.println("code: " + code);
|
||||
//System.out.println("***************");
|
||||
exe.sendLine(code);
|
||||
Thread.sleep(100);
|
||||
//stderr = exe.stderrString();
|
||||
//System.out.println("***************");
|
||||
//System.out.println(stderr);
|
||||
//System.out.println("***************");
|
||||
exe.waitForStderr("Login successful");
|
||||
exe.waitCompletion();
|
||||
Assert.assertEquals(0, exe.exitCode());
|
||||
Assert.assertEquals(0, exe.stdoutLines().size());
|
||||
|
||||
|
||||
exe = KcinitExec.execute("logout");
|
||||
Assert.assertEquals(0, exe.exitCode());
|
||||
|
||||
exe = KcinitExec.newBuilder()
|
||||
.argsLine("login")
|
||||
.executeAsync();
|
||||
exe.waitForStderr("Username:");
|
||||
exe.sendLine("wburke");
|
||||
exe.waitForStderr("Password:");
|
||||
exe.sendLine("password");
|
||||
exe.waitForStderr("One Time Password:");
|
||||
exe.sendLine(totp.generateTOTP(otpSecret));
|
||||
exe.waitForStderr("Login successful");
|
||||
exe.waitCompletion();
|
||||
|
||||
exe = KcinitExec.execute("logout");
|
||||
Assert.assertEquals(0, exe.exitCode());
|
||||
} finally {
|
||||
testingClient.server().run(session -> {
|
||||
RealmModel realm = session.realms().getRealmByName("test");
|
||||
UserModel user = session.users().getUserByUsername(realm, "wburke");
|
||||
session.userCredentialManager().getStoredCredentialsByTypeStream(realm, user, OTPCredentialModel.TYPE)
|
||||
.collect(Collectors.toList())
|
||||
.forEach(model -> session.userCredentialManager().removeStoredCredential(realm, user, model.getId()));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Rule
|
||||
public GreenMailRule greenMail = new GreenMailRule();
|
||||
|
||||
@Test
|
||||
public void testVerifyEmail() throws Exception {
|
||||
testingClient.server().run(session -> {
|
||||
RealmModel realm = session.realms().getRealmByName("test");
|
||||
UserModel user = session.users().getUserByUsername(realm, "test-user@localhost");
|
||||
user.addRequiredAction(UserModel.RequiredAction.VERIFY_EMAIL);
|
||||
});
|
||||
|
||||
testInstall();
|
||||
|
||||
KcinitExec exe = KcinitExec.newBuilder()
|
||||
.argsLine("login")
|
||||
.executeAsync();
|
||||
exe.waitForStderr("Username:");
|
||||
exe.sendLine("test-user@localhost");
|
||||
exe.waitForStderr("Password:");
|
||||
exe.sendLine("password");
|
||||
exe.waitForStderr("Email Code:");
|
||||
|
||||
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
|
||||
|
||||
MimeMessage message = greenMail.getReceivedMessages()[0];
|
||||
|
||||
String text = MailUtils.getBody(message).getText();
|
||||
Assert.assertTrue(text.contains("Please verify your email address by entering in the following code."));
|
||||
String code = text.substring("Please verify your email address by entering in the following code.".length()).trim();
|
||||
|
||||
exe.sendLine(code);
|
||||
|
||||
exe.waitForStderr("Login successful");
|
||||
exe.waitCompletion();
|
||||
Assert.assertEquals(0, exe.exitCode());
|
||||
Assert.assertEquals(0, exe.stdoutLines().size());
|
||||
|
||||
|
||||
exe = KcinitExec.execute("logout");
|
||||
Assert.assertEquals(0, exe.exitCode());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
Loading…
Reference in a new issue