Remove deprecated kcinit from keycloak

Closes #9106
This commit is contained in:
stianst 2021-11-08 13:32:26 +01:00 committed by Stian Thorgersen
parent d6abade872
commit 85240c9606
16 changed files with 12 additions and 1699 deletions

View file

@ -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();
}
}
}

View file

@ -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.

View file

@ -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>

View file

@ -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 $@

View file

@ -1,8 +0,0 @@
@echo off
if "%OS%" == "Windows_NT" (
set "DIRNAME=%~dp0%"
) else (
set DIRNAME=.\
)
java -jar %DIRNAME%\kcinit-${project.version}.jar %*

View file

@ -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);
}
}

View file

@ -34,7 +34,6 @@
<module>adapter-core</module> <module>adapter-core</module>
<module>installed</module> <module>installed</module>
<module>fuse7</module> <module>fuse7</module>
<module>kcinit</module>
<module>jaxrs-oauth-client</module> <module>jaxrs-oauth-client</module>
<module>jetty</module> <module>jetty</module>
<module>js</module> <module>js</module>

View file

@ -22,12 +22,5 @@ fi
DIRNAME=`dirname "$RESOLVED_NAME"` 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 "$@" java $KC_OPTS -cp $DIRNAME/client/keycloak-admin-cli-${project.version}.jar org.keycloak.client.admin.cli.KcAdmMain "$@"

11
pom.xml
View file

@ -1646,17 +1646,6 @@
<version>${project.version}</version> <version>${project.version}</version>
<type>zip</type> <type>zip</type>
</dependency> </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 --> <!-- webauthn support -->
<dependency> <dependency>
<groupId>com.webauthn4j</groupId> <groupId>com.webauthn4j</groupId>

View file

@ -124,11 +124,6 @@ public class OIDCLoginProtocolService {
return uriBuilder.path(OIDCLoginProtocolService.class, "registrations"); 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) { public static UriBuilder tokenUrl(UriBuilder baseUriBuilder) {
UriBuilder uriBuilder = tokenServiceBaseUrl(baseUriBuilder); UriBuilder uriBuilder = tokenServiceBaseUrl(baseUriBuilder);
return uriBuilder.path(OIDCLoginProtocolService.class, "token"); 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}") @Path("ext/{extension}")
public Object resolveExtension(@PathParam("extension") String extension) { public Object resolveExtension(@PathParam("extension") String extension) {
OIDCExtProvider provider = session.getProvider(OIDCExtProvider.class, extension); OIDCExtProvider provider = session.getProvider(OIDCExtProvider.class, extension);

View file

@ -288,43 +288,6 @@
</execution> </execution>
</executions> </executions>
</plugin> </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> <plugin>
<groupId>io.fabric8</groupId> <groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId> <artifactId>docker-maven-plugin</artifactId>

View file

@ -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;
}
}
}

View file

@ -1298,12 +1298,12 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest {
private String checkTokenExchange(boolean shouldPass) throws Exception { private String checkTokenExchange(boolean shouldPass) throws Exception {
testingClient.server().run(FineGrainAdminUnitTest::setupTokenExchange); testingClient.server().run(FineGrainAdminUnitTest::setupTokenExchange);
oauth.realm("master"); oauth.realm("master");
oauth.clientId("kcinit"); oauth.clientId("tokenexclient");
String exchanged = null; String exchanged = null;
String token = oauth.doGrantAccessTokenRequest("password", "admin", "admin").getAccessToken(); String token = oauth.doGrantAccessTokenRequest("password", "admin", "admin").getAccessToken();
Assert.assertNotNull(token); Assert.assertNotNull(token);
try { try {
exchanged = oauth.doTokenExchange("master", token, "admin-cli", "kcinit", "password").getAccessToken(); exchanged = oauth.doTokenExchange("master", token, "admin-cli", "tokenexclient", "password").getAccessToken();
} catch (AssertionError e) { } catch (AssertionError e) {
log.info("Error message is expected from oauth: " + e.getMessage()); 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) { private static void setupTokenExchange(KeycloakSession session) {
RealmModel realm = session.realms().getRealmByName("master"); RealmModel realm = session.realms().getRealmByName("master");
ClientModel client = session.clients().getClientByClientId(realm, "kcinit"); ClientModel client = session.clients().getClientByClientId(realm, "tokenexclient");
if (client != null) { if (client != null) {
return; return;
} }
ClientModel kcinit = realm.addClient("kcinit"); ClientModel tokenexclient = realm.addClient("tokenexclient");
kcinit.setEnabled(true); tokenexclient.setEnabled(true);
kcinit.addRedirectUri("http://localhost:*"); tokenexclient.addRedirectUri("http://localhost:*");
kcinit.setPublicClient(false); tokenexclient.setPublicClient(false);
kcinit.setSecret("password"); tokenexclient.setSecret("password");
kcinit.setDirectAccessGrantsEnabled(true); tokenexclient.setDirectAccessGrantsEnabled(true);
// permission for client to client exchange to "target" client // permission for client to client exchange to "target" client
ClientModel adminCli = realm.getClientByClientId(ConfigUtil.DEFAULT_CLIENT); ClientModel adminCli = realm.getClientByClientId(ConfigUtil.DEFAULT_CLIENT);
@ -1334,7 +1334,7 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest {
management.clients().setPermissionsEnabled(adminCli, true); management.clients().setPermissionsEnabled(adminCli, true);
ClientPolicyRepresentation clientRep = new ClientPolicyRepresentation(); ClientPolicyRepresentation clientRep = new ClientPolicyRepresentation();
clientRep.setName("to"); clientRep.setName("to");
clientRep.addClient(kcinit.getId()); clientRep.addClient(tokenexclient.getId());
ResourceServer server = management.realmResourceServer(); ResourceServer server = management.realmResourceServer();
Policy clientPolicy = management.authz().getStoreFactory().getPolicyStore().create(clientRep, server); Policy clientPolicy = management.authz().getStoreFactory().getPolicyStore().create(clientRep, server);
management.clients().exchangeToPermission(adminCli).addAssociatedPolicy(clientPolicy); management.clients().exchangeToPermission(adminCli).addAssociatedPolicy(clientPolicy);

View file

@ -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());
}
}