Remove text based login flows (#13249)
* Remove text based login flows Closes #8752 * Add display param back in case it's used by some custom authenticators
This commit is contained in:
parent
e14bd51656
commit
a251d785db
51 changed files with 30 additions and 2311 deletions
|
@ -309,273 +309,6 @@ public class KeycloakInstalled {
|
|||
status = Status.LOGGED_MANUAL;
|
||||
}
|
||||
|
||||
public static class Console {
|
||||
protected java.io.Console console = System.console();
|
||||
protected PrintWriter writer;
|
||||
protected BufferedReader reader;
|
||||
|
||||
static Console SINGLETON = new Console();
|
||||
|
||||
private Console() {
|
||||
}
|
||||
|
||||
|
||||
public PrintWriter writer() {
|
||||
if (console == null) {
|
||||
if (writer == null) {
|
||||
writer = new PrintWriter(System.err, true);
|
||||
}
|
||||
return writer;
|
||||
}
|
||||
return console.writer();
|
||||
}
|
||||
|
||||
public Reader reader() {
|
||||
if (console == null) {
|
||||
return getReader();
|
||||
}
|
||||
return console.reader();
|
||||
}
|
||||
|
||||
protected BufferedReader getReader() {
|
||||
if (reader != null) return reader;
|
||||
reader = new BufferedReader(new BufferedReader(new InputStreamReader(System.in)));
|
||||
return reader;
|
||||
}
|
||||
|
||||
public Console format(String fmt, Object... args) {
|
||||
if (console == null) {
|
||||
writer().format(fmt, args);
|
||||
return this;
|
||||
}
|
||||
console.format(fmt, args);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Console printf(String format, Object... args) {
|
||||
if (console == null) {
|
||||
writer().printf(format, args);
|
||||
return this;
|
||||
}
|
||||
console.printf(format, args);
|
||||
return this;
|
||||
}
|
||||
|
||||
public String readLine(String fmt, Object... args) {
|
||||
if (console == null) {
|
||||
format(fmt, args);
|
||||
return readLine();
|
||||
}
|
||||
return console.readLine(fmt, args);
|
||||
}
|
||||
|
||||
public boolean confirm(String fmt, Object... args) {
|
||||
String prompt = "";
|
||||
while (!"y".equals(prompt) && !"n".equals(prompt)) {
|
||||
prompt = readLine(fmt, args);
|
||||
}
|
||||
return "y".equals(prompt);
|
||||
|
||||
}
|
||||
|
||||
public String prompt(String fmt, Object... args) {
|
||||
String prompt = "";
|
||||
while (prompt.equals("")) {
|
||||
prompt = readLine(fmt, args).trim();
|
||||
}
|
||||
return prompt;
|
||||
|
||||
}
|
||||
|
||||
public String passwordPrompt(String fmt, Object... args) {
|
||||
String prompt = "";
|
||||
while (prompt.equals("")) {
|
||||
char[] val = readPassword(fmt, args);
|
||||
prompt = new String(val);
|
||||
prompt = prompt.trim();
|
||||
}
|
||||
return prompt;
|
||||
|
||||
}
|
||||
|
||||
public String readLine() {
|
||||
if (console == null) {
|
||||
try {
|
||||
return getReader().readLine();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return console.readLine();
|
||||
}
|
||||
|
||||
public char[] readPassword(String fmt, Object... args) {
|
||||
if (console == null) {
|
||||
return readLine(fmt, args).toCharArray();
|
||||
|
||||
}
|
||||
return console.readPassword(fmt, args);
|
||||
}
|
||||
|
||||
public char[] readPassword() {
|
||||
if (console == null) {
|
||||
return readLine().toCharArray();
|
||||
}
|
||||
return console.readPassword();
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
if (console == null) {
|
||||
System.err.flush();
|
||||
return;
|
||||
}
|
||||
console.flush();
|
||||
}
|
||||
|
||||
public void stderrOutput() {
|
||||
//System.err.println("not using System.console()");
|
||||
console = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Console console() {
|
||||
return Console.SINGLETON;
|
||||
}
|
||||
|
||||
public boolean loginCommandLine() throws IOException, ServerRequest.HttpFailure, VerificationException {
|
||||
String redirectUri = "urn:ietf:wg:oauth:2.0:oob";
|
||||
|
||||
return loginCommandLine(redirectUri);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Experimental proprietary WWW-Authentication challenge protocol.
|
||||
* WWW-Authentication: X-Text-Form-Challenge callback="{url}" param="{param-name}" label="{param-display-label}"
|
||||
*
|
||||
* @param redirectUri
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws ServerRequest.HttpFailure
|
||||
* @throws VerificationException
|
||||
*/
|
||||
public boolean loginCommandLine(String redirectUri) throws IOException, ServerRequest.HttpFailure, VerificationException {
|
||||
String authUrl = deployment.getAuthUrl().clone()
|
||||
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
|
||||
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
|
||||
.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri)
|
||||
.queryParam("display", "console")
|
||||
.queryParam(OAuth2Constants.SCOPE, OAuth2Constants.SCOPE_OPENID)
|
||||
.build().toString();
|
||||
ResteasyClient client = createResteasyClient();
|
||||
try {
|
||||
//System.err.println("initial request");
|
||||
Response response = client.target(authUrl).request().get();
|
||||
while (true) {
|
||||
if (response.getStatus() == 403) {
|
||||
if (response.getMediaType() != null) {
|
||||
String splash = response.readEntity(String.class);
|
||||
console().writer().println(splash);
|
||||
} else {
|
||||
System.err.println("Forbidden to login");
|
||||
}
|
||||
return false;
|
||||
} else if (response.getStatus() == 401) {
|
||||
String authenticationHeader = response.getHeaderString(HttpHeaders.WWW_AUTHENTICATE);
|
||||
if (authenticationHeader == null) {
|
||||
System.err.println("Failure: Invalid protocol. No WWW-Authenticate header");
|
||||
return false;
|
||||
}
|
||||
//System.err.println("got header: " + authenticationHeader);
|
||||
if (!authenticationHeader.contains("X-Text-Form-Challenge")) {
|
||||
System.err.println("Failure: Invalid WWW-Authenticate header.");
|
||||
return false;
|
||||
}
|
||||
if (response.getMediaType() != null) {
|
||||
String splash = response.readEntity(String.class);
|
||||
console().writer().println(splash);
|
||||
} else {
|
||||
response.close();
|
||||
}
|
||||
Matcher m = callbackPattern.matcher(authenticationHeader);
|
||||
if (!m.find()) {
|
||||
System.err.println("Failure: Invalid WWW-Authenticate header.");
|
||||
return false;
|
||||
}
|
||||
String callback = m.group(1);
|
||||
//System.err.println("callback: " + callback);
|
||||
m = paramPattern.matcher(authenticationHeader);
|
||||
Form form = new Form();
|
||||
while (m.find()) {
|
||||
String param = m.group(1);
|
||||
String label = m.group(2);
|
||||
String mask = m.group(3).trim();
|
||||
boolean maskInput = mask.equals("true");
|
||||
String value = null;
|
||||
if (maskInput) {
|
||||
char[] txt = console().readPassword(label);
|
||||
value = new String(txt);
|
||||
} else {
|
||||
value = console().readLine(label);
|
||||
}
|
||||
form.param(param, value);
|
||||
}
|
||||
response.close();
|
||||
client.close();
|
||||
client = createResteasyClient();
|
||||
response = client.target(callback).request().post(Entity.form(form));
|
||||
} else if (response.getStatus() == 302) {
|
||||
int redirectCount = 0;
|
||||
do {
|
||||
String location = response.getLocation().toString();
|
||||
Matcher m = codePattern.matcher(location);
|
||||
if (!m.find()) {
|
||||
response.close();
|
||||
client.close();
|
||||
client = createResteasyClient();
|
||||
response = client.target(location).request().get();
|
||||
} else {
|
||||
response.close();
|
||||
client.close();
|
||||
String code = m.group(1);
|
||||
processCode(code, redirectUri, null);
|
||||
return true;
|
||||
}
|
||||
if (response.getStatus() == 302 && redirectCount++ > 4) {
|
||||
System.err.println("Too many redirects. Aborting");
|
||||
return false;
|
||||
}
|
||||
} while (response.getStatus() == 302);
|
||||
} else {
|
||||
System.err.println("Unknown response from server: " + response.getStatus());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw ex;
|
||||
} finally {
|
||||
client.close();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
protected ResteasyClient getResteasyClient() {
|
||||
if (this.resteasyClient == null) {
|
||||
this.resteasyClient = createResteasyClient();
|
||||
}
|
||||
return this.resteasyClient;
|
||||
}
|
||||
|
||||
protected ResteasyClient createResteasyClient() {
|
||||
return ((ResteasyClientBuilder) ResteasyClientBuilder.newBuilder())
|
||||
.connectionCheckoutTimeout(1, TimeUnit.HOURS)
|
||||
.connectionTTL(1, TimeUnit.HOURS)
|
||||
.connectTimeout(1, TimeUnit.HOURS)
|
||||
.readTimeout(1, TimeUnit.HOURS)
|
||||
.disableTrustManager().build();
|
||||
}
|
||||
|
||||
|
||||
public String getTokenString() {
|
||||
return tokenString;
|
||||
}
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
package org.keycloak.common.util;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* @deprecated Replaced by {@link SecretGenerator}
|
||||
*/
|
||||
@Deprecated
|
||||
public class RandomString {
|
||||
|
||||
/**
|
||||
* Generate a random string.
|
||||
*/
|
||||
public String nextString() {
|
||||
for (int idx = 0; idx < buf.length; ++idx)
|
||||
buf[idx] = symbols[random.nextInt(symbols.length)];
|
||||
return new String(buf);
|
||||
}
|
||||
|
||||
public static final String upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
|
||||
public static final String lower = upper.toLowerCase(Locale.ROOT);
|
||||
|
||||
public static final String digits = "0123456789";
|
||||
|
||||
public static final String alphanum = upper + lower + digits;
|
||||
|
||||
private final Random random;
|
||||
|
||||
private final char[] symbols;
|
||||
|
||||
private final char[] buf;
|
||||
|
||||
public RandomString(int length, Random random, String symbols) {
|
||||
if (length < 1) throw new IllegalArgumentException();
|
||||
if (symbols.length() < 2) throw new IllegalArgumentException();
|
||||
this.random = Objects.requireNonNull(random);
|
||||
this.symbols = symbols.toCharArray();
|
||||
this.buf = new char[length];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an alphanumeric string generator.
|
||||
*/
|
||||
public RandomString(int length, Random random) {
|
||||
this(length, random, alphanum);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an alphanumeric strings from a secure generator.
|
||||
*/
|
||||
public RandomString(int length) {
|
||||
this(length, new SecureRandom());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create session identifiers.
|
||||
*/
|
||||
public RandomString() {
|
||||
this(21);
|
||||
}
|
||||
|
||||
public static String randomCode(int length) {
|
||||
return new RandomString(length).nextString();
|
||||
}
|
||||
|
||||
}
|
|
@ -140,7 +140,6 @@ public interface OAuth2Constants {
|
|||
|
||||
String CIBA_GRANT_TYPE = "urn:openid:params:grant-type:ciba";
|
||||
|
||||
String DISPLAY_CONSOLE = "console";
|
||||
String INTERVAL = "interval";
|
||||
String USER_CODE = "user_code";
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
package org.keycloak.jose.jwe;
|
||||
|
||||
import org.keycloak.common.util.Base64;
|
||||
import org.keycloak.common.util.Base64Url;
|
||||
import org.keycloak.jose.JOSE;
|
||||
import org.keycloak.jose.JOSEHeader;
|
||||
|
@ -25,13 +24,7 @@ import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
|
|||
import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.spec.KeySpec;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
|
@ -232,56 +225,4 @@ public class JWE implements JOSE {
|
|||
}
|
||||
}
|
||||
|
||||
public static String encryptUTF8(String password, String saltString, String payload) {
|
||||
byte[] bytes = payload.getBytes(StandardCharsets.UTF_8);
|
||||
return encrypt(password, saltString, bytes);
|
||||
}
|
||||
|
||||
|
||||
public static String encrypt(String password, String saltString, byte[] payload) {
|
||||
try {
|
||||
byte[] salt = Base64.decode(saltString);
|
||||
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
|
||||
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 100, 128);
|
||||
SecretKey tmp = factory.generateSecret(spec);
|
||||
SecretKey aesKey = new SecretKeySpec(tmp.getEncoded(), "AES");
|
||||
|
||||
JWEHeader jweHeader = new JWEHeader(JWEConstants.A128KW, JWEConstants.A128CBC_HS256, null);
|
||||
JWE jwe = new JWE()
|
||||
.header(jweHeader)
|
||||
.content(payload);
|
||||
|
||||
jwe.getKeyStorage()
|
||||
.setEncryptionKey(aesKey);
|
||||
|
||||
return jwe.encodeJwe();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] decrypt(String password, String saltString, String encodedJwe) {
|
||||
try {
|
||||
byte[] salt = Base64.decode(saltString);
|
||||
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
|
||||
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 100, 128);
|
||||
SecretKey tmp = factory.generateSecret(spec);
|
||||
SecretKey aesKey = new SecretKeySpec(tmp.getEncoded(), "AES");
|
||||
|
||||
JWE jwe = new JWE();
|
||||
jwe.getKeyStorage()
|
||||
.setDecryptionKey(aesKey);
|
||||
|
||||
jwe.verifyAndDecodeJwe(encodedJwe);
|
||||
return jwe.getContent();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String decryptUTF8(String password, String saltString, String encodedJwe) {
|
||||
byte[] payload = decrypt(password, saltString, encodedJwe);
|
||||
return new String(payload, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,22 +17,18 @@
|
|||
|
||||
package org.keycloak.jose;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.common.util.Base64;
|
||||
import org.keycloak.common.util.Base64Url;
|
||||
import org.keycloak.common.util.KeyUtils;
|
||||
import org.keycloak.jose.jwe.*;
|
||||
import org.keycloak.jose.jwe.JWE;
|
||||
import org.keycloak.jose.jwe.JWEConstants;
|
||||
import org.keycloak.jose.jwe.JWEException;
|
||||
import org.keycloak.jose.jwe.JWEHeader;
|
||||
import org.keycloak.jose.jwe.JWEKeyStorage;
|
||||
import org.keycloak.jose.jwe.JWEUtils;
|
||||
import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
|
||||
import org.keycloak.jose.jwe.alg.RsaKeyEncryptionJWEAlgorithmProvider;
|
||||
import org.keycloak.jose.jwe.enc.AesCbcHmacShaJWEEncryptionProvider;
|
||||
|
@ -40,6 +36,12 @@ import org.keycloak.jose.jwe.enc.AesGcmJWEEncryptionProvider;
|
|||
import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
|
||||
import org.keycloak.rule.CryptoInitRule;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
|
||||
/**
|
||||
* This is not tested in keycloak-core. The subclasses should be created in the crypto modules to make sure it is tested with corresponding modules (bouncycastle VS bouncycastle-fips)
|
||||
*
|
||||
|
@ -132,17 +134,6 @@ public abstract class JWETest {
|
|||
System.out.println("Iterations: " + iterations + ", took: " + took);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPassword() {
|
||||
byte[] salt = JWEUtils.generateSecret(8);
|
||||
String encodedSalt = Base64.encodeBytes(salt);
|
||||
String jwe = JWE.encryptUTF8("geheim", encodedSalt, PAYLOAD);
|
||||
String decodedContent = JWE.decryptUTF8("geheim", encodedSalt, jwe);
|
||||
Assert.assertEquals(PAYLOAD, decodedContent);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testAesKW_Aes128CbcHmacSha256() throws Exception {
|
||||
SecretKey aesKey = new SecretKeySpec(AES_128_KEY, "AES");
|
||||
|
|
|
@ -89,15 +89,6 @@ public interface AuthenticationFlowContext extends AbstractAuthenticationFlowCon
|
|||
*/
|
||||
URI getActionUrl(String code);
|
||||
|
||||
/**
|
||||
* Get the action URL for the required action.
|
||||
*
|
||||
* @param code authentication session access code
|
||||
* @param authSessionIdParam will include auth_session query param for clients that don't process cookies
|
||||
* @return
|
||||
*/
|
||||
URI getActionUrl(String code, boolean authSessionIdParam);
|
||||
|
||||
/**
|
||||
* Get the action URL for the action token executor.
|
||||
*
|
||||
|
|
|
@ -1,324 +0,0 @@
|
|||
package org.keycloak.authentication;
|
||||
|
||||
import org.keycloak.forms.login.LoginFormsProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
/**
|
||||
* This class encapsulates a proprietary HTTP challenge protocol designed by keycloak team which is used by text-based console
|
||||
* clients to dynamically render and prompt for information in a textual manner. The class is a builder which can
|
||||
* build the challenge response (the header and response body).
|
||||
*
|
||||
* When doing code to token flow in OAuth, server could respond with
|
||||
*
|
||||
* 401
|
||||
* WWW-Authenticate: X-Text-Form-Challenge callback="http://localhost/..."
|
||||
* param="username" label="Username: " mask=false
|
||||
* param="password" label="Password: " mask=true
|
||||
* Content-Type: text/plain
|
||||
*
|
||||
* Please login with your username and password
|
||||
*
|
||||
*
|
||||
* The client receives this challenge. It first outputs whatever the text body of the message contains. It will
|
||||
* then prompt for username and password using the label values as prompt messages for each parameter.
|
||||
*
|
||||
* After the input has been entered by the user, the client does a form POST to the callback url with the values of the
|
||||
* input parameters entered.
|
||||
*
|
||||
* The server can challenge with 401 as many times as it wants. The client will look for 302 responses. It will will
|
||||
* follow all redirects unless the Location url has an OAuth "code" parameter. If there is a code parameter, then the
|
||||
* client will stop and finish the OAuth flow to obtain a token. Any other response code other than 401 or 302 the client
|
||||
* should abort with an error message.
|
||||
*
|
||||
*/
|
||||
public class ConsoleDisplayMode {
|
||||
|
||||
/**
|
||||
* Browser is required to login. This will abort client from doing a console login.
|
||||
*
|
||||
* @param session
|
||||
* @return
|
||||
*/
|
||||
public static Response browserRequired(KeycloakSession session) {
|
||||
return Response.status(Response.Status.UNAUTHORIZED)
|
||||
.header("WWW-Authenticate", "X-Text-Form-Challenge browserRequired")
|
||||
.type(MediaType.TEXT_PLAIN)
|
||||
.entity("\n" + session.getProvider(LoginFormsProvider.class).getMessage("browserRequired") + "\n").build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Browser is required to continue login. This will prompt client on whether to continue with a browser or abort.
|
||||
*
|
||||
* @param session
|
||||
* @param callback
|
||||
* @return
|
||||
*/
|
||||
public static Response browserContinue(KeycloakSession session, String callback) {
|
||||
String browserContinueMsg = session.getProvider(LoginFormsProvider.class).getMessage("browserContinue");
|
||||
String browserPrompt = session.getProvider(LoginFormsProvider.class).getMessage("browserContinuePrompt");
|
||||
String answer = session.getProvider(LoginFormsProvider.class).getMessage("browserContinueAnswer");
|
||||
|
||||
String header = "X-Text-Form-Challenge callback=\"" + callback + "\"";
|
||||
header += " browserContinue=\"" + browserPrompt + "\" answer=\"" + answer + "\"";
|
||||
return Response.status(Response.Status.UNAUTHORIZED)
|
||||
.header("WWW-Authenticate", header)
|
||||
.type(MediaType.TEXT_PLAIN)
|
||||
.entity("\n" + browserContinueMsg + "\n").build();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Build challenge response for required actions
|
||||
*
|
||||
* @param context
|
||||
* @return
|
||||
*/
|
||||
public static ConsoleDisplayMode challenge(RequiredActionContext context) {
|
||||
return new ConsoleDisplayMode(context);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Build challenge response for authentication flows
|
||||
*
|
||||
* @param context
|
||||
* @return
|
||||
*/
|
||||
public static ConsoleDisplayMode challenge(AuthenticationFlowContext context) {
|
||||
return new ConsoleDisplayMode(context);
|
||||
|
||||
}
|
||||
/**
|
||||
* Build challenge response header only for required actions
|
||||
*
|
||||
* @param context
|
||||
* @return
|
||||
*/
|
||||
public static HeaderBuilder header(RequiredActionContext context) {
|
||||
return new ConsoleDisplayMode(context).header();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Build challenge response header only for authentication flows
|
||||
*
|
||||
* @param context
|
||||
* @return
|
||||
*/
|
||||
public static HeaderBuilder header(AuthenticationFlowContext context) {
|
||||
return new ConsoleDisplayMode(context).header();
|
||||
|
||||
}
|
||||
ConsoleDisplayMode(RequiredActionContext requiredActionContext) {
|
||||
this.requiredActionContext = requiredActionContext;
|
||||
}
|
||||
|
||||
ConsoleDisplayMode(AuthenticationFlowContext flowContext) {
|
||||
this.flowContext = flowContext;
|
||||
}
|
||||
|
||||
|
||||
protected RequiredActionContext requiredActionContext;
|
||||
protected AuthenticationFlowContext flowContext;
|
||||
protected HeaderBuilder header;
|
||||
|
||||
/**
|
||||
* Create a theme form pre-populated with challenge
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public LoginFormsProvider form() {
|
||||
if (header == null) throw new RuntimeException("Header Not Set");
|
||||
return formInternal()
|
||||
.setStatus(Response.Status.UNAUTHORIZED)
|
||||
.setMediaType(MediaType.TEXT_PLAIN_TYPE)
|
||||
.setResponseHeader(HttpHeaders.WWW_AUTHENTICATE, header.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create challenge response with a body generated from localized
|
||||
* message.properties of your theme
|
||||
*
|
||||
* @param msg message id
|
||||
* @param params parameters to use to format the message
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Response message(String msg, String... params) {
|
||||
if (header == null) throw new RuntimeException("Header Not Set");
|
||||
Response response = Response.status(401)
|
||||
.header(HttpHeaders.WWW_AUTHENTICATE, header.build())
|
||||
.type(MediaType.TEXT_PLAIN)
|
||||
.entity("\n" + formInternal().getMessage(msg, params) + "\n").build();
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create challenge response with a text message body
|
||||
*
|
||||
* @param text plain text of http response body
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Response text(String text) {
|
||||
if (header == null) throw new RuntimeException("Header Not Set");
|
||||
Response response = Response.status(401)
|
||||
.header(HttpHeaders.WWW_AUTHENTICATE, header.build())
|
||||
.type(MediaType.TEXT_PLAIN)
|
||||
.entity("\n" + text + "\n").build();
|
||||
return response;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate response with empty http response body
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Response response() {
|
||||
if (header == null) throw new RuntimeException("Header Not Set");
|
||||
Response response = Response.status(401)
|
||||
.header(HttpHeaders.WWW_AUTHENTICATE, header.build()).build();
|
||||
return response;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected LoginFormsProvider formInternal() {
|
||||
if (requiredActionContext != null) {
|
||||
return requiredActionContext.form();
|
||||
} else {
|
||||
return flowContext.form();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start building the header
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public HeaderBuilder header() {
|
||||
String callback;
|
||||
if (requiredActionContext != null) {
|
||||
callback = requiredActionContext.getActionUrl(true).toString();
|
||||
} else {
|
||||
callback = flowContext.getActionUrl(flowContext.generateAccessCode(), true).toString();
|
||||
|
||||
}
|
||||
header = new HeaderBuilder(callback);
|
||||
return header;
|
||||
}
|
||||
|
||||
public class HeaderBuilder {
|
||||
protected StringBuilder builder = new StringBuilder();
|
||||
|
||||
protected HeaderBuilder(String callback) {
|
||||
builder.append("X-Text-Form-Challenge callback=\"").append(callback).append("\" ");
|
||||
}
|
||||
|
||||
protected ParamBuilder param;
|
||||
|
||||
protected void checkParam() {
|
||||
if (param != null) {
|
||||
param.buildInternal();
|
||||
param = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build header string
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String build() {
|
||||
checkParam();
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a param
|
||||
*
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
public ParamBuilder param(String name) {
|
||||
checkParam();
|
||||
builder.append("param=\"").append(name).append("\" ");
|
||||
param = new ParamBuilder(name);
|
||||
return param;
|
||||
}
|
||||
|
||||
public class ParamBuilder {
|
||||
protected boolean mask;
|
||||
protected String label;
|
||||
|
||||
protected ParamBuilder(String name) {
|
||||
this.label = name;
|
||||
}
|
||||
|
||||
public ParamBuilder label(String msg) {
|
||||
this.label = formInternal().getMessage(msg);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ParamBuilder labelText(String txt) {
|
||||
this.label = txt;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should input be masked by the client. For example, when entering password, you don't want to show password on console.
|
||||
*
|
||||
* @param mask
|
||||
* @return
|
||||
*/
|
||||
public ParamBuilder mask(boolean mask) {
|
||||
this.mask = mask;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void buildInternal() {
|
||||
builder.append("label=\"").append(label).append(" \" ");
|
||||
builder.append("mask=").append(mask).append(" ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Build header string
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String build() {
|
||||
return HeaderBuilder.this.build();
|
||||
}
|
||||
|
||||
public ConsoleDisplayMode challenge() {
|
||||
return ConsoleDisplayMode.this;
|
||||
}
|
||||
|
||||
public LoginFormsProvider form() {
|
||||
return ConsoleDisplayMode.this.form();
|
||||
}
|
||||
|
||||
public Response message(String msg, String... params) {
|
||||
return ConsoleDisplayMode.this.message(msg, params);
|
||||
}
|
||||
|
||||
public Response text(String text) {
|
||||
return ConsoleDisplayMode.this.text(text);
|
||||
|
||||
}
|
||||
|
||||
public ParamBuilder param(String name) {
|
||||
return HeaderBuilder.this.param(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
package org.keycloak.authentication;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
/**
|
||||
* Implement this interface when declaring your authenticator factory
|
||||
* if your provider has support for multiple oidc display query parameter parameter types
|
||||
* if the display query parameter is set and your factory implements this interface, this method
|
||||
* will be called.
|
||||
*
|
||||
*/
|
||||
public interface DisplayTypeAuthenticatorFactory {
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param session
|
||||
* @param displayType i.e. "console", "wap", "popup" are examples
|
||||
* @return null if display type isn't support.
|
||||
*/
|
||||
Authenticator createDisplay(KeycloakSession session, String displayType);
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
package org.keycloak.authentication;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
/**
|
||||
* Implement this interface when declaring your required action factory
|
||||
* has support for multiple oidc display query parameter parameter types
|
||||
* if the display query parameter is set and your factory implements this interface, this method
|
||||
* will be called.
|
||||
*/
|
||||
public interface DisplayTypeRequiredActionFactory {
|
||||
RequiredActionProvider createDisplay(KeycloakSession session, String displayType);
|
||||
}
|
|
@ -65,15 +65,6 @@ public interface RequiredActionContext {
|
|||
*/
|
||||
URI getActionUrl();
|
||||
|
||||
/**
|
||||
* Get the action URL for the required action. This auto-generates the access code.
|
||||
*
|
||||
* @param authSessionIdParam if true, will embed session id as query param. Useful for clients that don't support cookies (i.e. console)
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
URI getActionUrl(boolean authSessionIdParam);
|
||||
|
||||
/**
|
||||
* Create a Freemarker form builder that presets the user, action URI, and a generated access code
|
||||
*
|
||||
|
|
|
@ -56,8 +56,6 @@ public interface LoginFormsProvider extends Provider {
|
|||
|
||||
String getMessage(String message);
|
||||
|
||||
String getMessage(String message, String... parameters);
|
||||
|
||||
Response createLoginUsernamePassword();
|
||||
|
||||
Response createLoginUsername();
|
||||
|
@ -149,8 +147,6 @@ public interface LoginFormsProvider extends Provider {
|
|||
|
||||
LoginFormsProvider setStatus(Response.Status status);
|
||||
|
||||
LoginFormsProvider setMediaType(javax.ws.rs.core.MediaType type);
|
||||
|
||||
LoginFormsProvider setActionUri(URI requestUri);
|
||||
|
||||
LoginFormsProvider setExecution(String execution);
|
||||
|
|
|
@ -73,7 +73,6 @@ public final class Constants {
|
|||
public static final String WEBAUTHN_PASSWORDLESS_PREFIX = "Passwordless";
|
||||
|
||||
public static final String VERIFY_EMAIL_KEY = "VERIFY_EMAIL_KEY";
|
||||
public static final String VERIFY_EMAIL_CODE = "VERIFY_EMAIL_CODE";
|
||||
public static final String EXECUTION = "execution";
|
||||
public static final String CLIENT_ID = "client_id";
|
||||
public static final String TAB_ID = "tab_id";
|
||||
|
|
|
@ -587,21 +587,6 @@ public class AuthenticationProcessor {
|
|||
.build(getRealm().getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getActionUrl(String code, boolean authSessionIdParam) {
|
||||
UriBuilder uriBuilder = LoginActionsService.loginActionsBaseUrl(getUriInfo())
|
||||
.path(AuthenticationProcessor.this.flowPath)
|
||||
.queryParam(LoginActionsService.SESSION_CODE, code)
|
||||
.queryParam(Constants.EXECUTION, getExecution().getId())
|
||||
.queryParam(Constants.CLIENT_ID, getAuthenticationSession().getClient().getClientId())
|
||||
.queryParam(Constants.TAB_ID, getAuthenticationSession().getTabId());
|
||||
if (authSessionIdParam) {
|
||||
uriBuilder.queryParam(LoginActionsService.AUTH_SESSION_ID, getAuthenticationSession().getParentSession().getId());
|
||||
}
|
||||
return uriBuilder
|
||||
.build(getRealm().getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getRefreshExecutionUrl() {
|
||||
UriBuilder uriBuilder = LoginActionsService.loginActionsBaseUrl(getUriInfo())
|
||||
|
|
|
@ -74,22 +74,8 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
|
|||
}
|
||||
|
||||
protected Authenticator createAuthenticator(AuthenticatorFactory factory) {
|
||||
String display = processor.getAuthenticationSession().getAuthNote(OAuth2Constants.DISPLAY);
|
||||
if (display == null) return factory.create(processor.getSession());
|
||||
|
||||
if (factory instanceof DisplayTypeAuthenticatorFactory) {
|
||||
Authenticator authenticator = ((DisplayTypeAuthenticatorFactory) factory).createDisplay(processor.getSession(), display);
|
||||
if (authenticator != null) return authenticator;
|
||||
}
|
||||
// todo create a provider for handling lack of display support
|
||||
if (OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(display)) {
|
||||
processor.getAuthenticationSession().removeAuthNote(OAuth2Constants.DISPLAY);
|
||||
throw new AuthenticationFlowException(AuthenticationFlowError.DISPLAY_NOT_SUPPORTED,
|
||||
ConsoleDisplayMode.browserContinue(processor.getSession(), processor.getRefreshUrl(true).toString()));
|
||||
} else {
|
||||
return factory.create(processor.getSession());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response processAction(String actionExecution) {
|
||||
|
|
|
@ -166,15 +166,6 @@ public class RequiredActionContextResult implements RequiredActionContext {
|
|||
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getActionUrl(boolean authSessionIdParam) {
|
||||
URI uri = getActionUrl();
|
||||
if (authSessionIdParam) {
|
||||
uri = UriBuilder.fromUri(uri).queryParam(LoginActionsService.AUTH_SESSION_ID, getAuthenticationSession().getParentSession().getId()).build();
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoginFormsProvider form() {
|
||||
String accessCode = generateCode();
|
||||
|
|
|
@ -18,11 +18,8 @@
|
|||
package org.keycloak.authentication.authenticators.browser;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.AuthenticatorFactory;
|
||||
import org.keycloak.authentication.DisplayTypeAuthenticatorFactory;
|
||||
import org.keycloak.authentication.authenticators.AttemptedAuthenticator;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
@ -34,7 +31,7 @@ import java.util.List;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class CookieAuthenticatorFactory implements AuthenticatorFactory, DisplayTypeAuthenticatorFactory {
|
||||
public class CookieAuthenticatorFactory implements AuthenticatorFactory {
|
||||
public static final String PROVIDER_ID = "auth-cookie";
|
||||
static CookieAuthenticator SINGLETON = new CookieAuthenticator();
|
||||
|
||||
|
@ -43,13 +40,6 @@ public class CookieAuthenticatorFactory implements AuthenticatorFactory, Display
|
|||
return SINGLETON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authenticator createDisplay(KeycloakSession session, String displayType) {
|
||||
if (displayType == null) return SINGLETON;
|
||||
if (!OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(displayType)) return null;
|
||||
return AttemptedAuthenticator.SINGLETON; // ignore this authenticator
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
|
|
|
@ -84,9 +84,6 @@ public class IdentityProviderAuthenticator implements Authenticator {
|
|||
String clientId = context.getAuthenticationSession().getClient().getClientId();
|
||||
String tabId = context.getAuthenticationSession().getTabId();
|
||||
URI location = Urls.identityProviderAuthnRequest(context.getUriInfo().getBaseUri(), providerId, context.getRealm().getName(), accessCode, clientId, tabId);
|
||||
if (context.getAuthenticationSession().getClientNote(OAuth2Constants.DISPLAY) != null) {
|
||||
location = UriBuilder.fromUri(location).queryParam(OAuth2Constants.DISPLAY, context.getAuthenticationSession().getClientNote(OAuth2Constants.DISPLAY)).build();
|
||||
}
|
||||
Response response = Response.seeOther(location)
|
||||
.build();
|
||||
// will forward the request to the IDP with prompt=none if the IDP accepts forwards with prompt=none.
|
||||
|
|
|
@ -18,11 +18,8 @@
|
|||
package org.keycloak.authentication.authenticators.browser;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.AuthenticatorFactory;
|
||||
import org.keycloak.authentication.DisplayTypeAuthenticatorFactory;
|
||||
import org.keycloak.authentication.authenticators.AttemptedAuthenticator;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
@ -36,7 +33,7 @@ import static org.keycloak.provider.ProviderConfigProperty.STRING_TYPE;
|
|||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class IdentityProviderAuthenticatorFactory implements AuthenticatorFactory, DisplayTypeAuthenticatorFactory {
|
||||
public class IdentityProviderAuthenticatorFactory implements AuthenticatorFactory {
|
||||
protected static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
|
||||
AuthenticationExecutionModel.Requirement.REQUIRED, AuthenticationExecutionModel.Requirement.ALTERNATIVE, AuthenticationExecutionModel.Requirement.DISABLED
|
||||
};
|
||||
|
@ -85,13 +82,6 @@ public class IdentityProviderAuthenticatorFactory implements AuthenticatorFactor
|
|||
return new IdentityProviderAuthenticator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authenticator createDisplay(KeycloakSession session, String displayType) {
|
||||
if (displayType == null) return new IdentityProviderAuthenticator();
|
||||
if (!OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(displayType)) return null;
|
||||
return AttemptedAuthenticator.SINGLETON; // ignore this authenticator
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
}
|
||||
|
|
|
@ -18,11 +18,8 @@
|
|||
package org.keycloak.authentication.authenticators.browser;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.AuthenticatorFactory;
|
||||
import org.keycloak.authentication.DisplayTypeAuthenticatorFactory;
|
||||
import org.keycloak.authentication.authenticators.console.ConsoleOTPFormAuthenticator;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
@ -35,7 +32,7 @@ import java.util.List;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class OTPFormAuthenticatorFactory implements AuthenticatorFactory, DisplayTypeAuthenticatorFactory {
|
||||
public class OTPFormAuthenticatorFactory implements AuthenticatorFactory {
|
||||
|
||||
public static final String PROVIDER_ID = "auth-otp-form";
|
||||
public static final OTPFormAuthenticator SINGLETON = new OTPFormAuthenticator();
|
||||
|
@ -45,13 +42,6 @@ public class OTPFormAuthenticatorFactory implements AuthenticatorFactory, Displa
|
|||
return SINGLETON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authenticator createDisplay(KeycloakSession session, String displayType) {
|
||||
if (displayType == null) return SINGLETON;
|
||||
if (!OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(displayType)) return null;
|
||||
return ConsoleOTPFormAuthenticator.SINGLETON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
|
|
|
@ -18,11 +18,8 @@
|
|||
package org.keycloak.authentication.authenticators.browser;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.AuthenticatorFactory;
|
||||
import org.keycloak.authentication.DisplayTypeAuthenticatorFactory;
|
||||
import org.keycloak.authentication.authenticators.console.ConsolePasswordAuthenticator;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
@ -35,7 +32,7 @@ import java.util.List;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class PasswordFormFactory implements AuthenticatorFactory, DisplayTypeAuthenticatorFactory {
|
||||
public class PasswordFormFactory implements AuthenticatorFactory {
|
||||
|
||||
public static final String PROVIDER_ID = "auth-password-form";
|
||||
public static final PasswordForm SINGLETON = new PasswordForm();
|
||||
|
@ -45,13 +42,6 @@ public class PasswordFormFactory implements AuthenticatorFactory, DisplayTypeAut
|
|||
return SINGLETON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authenticator createDisplay(KeycloakSession session, String displayType) {
|
||||
if (displayType == null) return SINGLETON;
|
||||
if (!OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(displayType)) return null;
|
||||
return ConsolePasswordAuthenticator.SINGLETON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
|
|
|
@ -18,10 +18,8 @@
|
|||
package org.keycloak.authentication.authenticators.browser;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.AuthenticatorFactory;
|
||||
import org.keycloak.authentication.DisplayTypeAuthenticatorFactory;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
@ -34,7 +32,7 @@ import java.util.List;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class SpnegoAuthenticatorFactory implements AuthenticatorFactory, DisplayTypeAuthenticatorFactory {
|
||||
public class SpnegoAuthenticatorFactory implements AuthenticatorFactory {
|
||||
|
||||
public static final String PROVIDER_ID = "auth-spnego";
|
||||
public static final SpnegoAuthenticator SINGLETON = new SpnegoAuthenticator();
|
||||
|
@ -44,13 +42,6 @@ public class SpnegoAuthenticatorFactory implements AuthenticatorFactory, Display
|
|||
return SINGLETON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authenticator createDisplay(KeycloakSession session, String displayType) {
|
||||
if (displayType == null) return SINGLETON;
|
||||
if (!OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(displayType)) return null;
|
||||
return SINGLETON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
|
|
|
@ -18,11 +18,8 @@
|
|||
package org.keycloak.authentication.authenticators.browser;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.AuthenticatorFactory;
|
||||
import org.keycloak.authentication.DisplayTypeAuthenticatorFactory;
|
||||
import org.keycloak.authentication.authenticators.console.ConsoleUsernameAuthenticator;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
@ -35,7 +32,7 @@ import java.util.List;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class UsernameFormFactory implements AuthenticatorFactory, DisplayTypeAuthenticatorFactory {
|
||||
public class UsernameFormFactory implements AuthenticatorFactory {
|
||||
|
||||
public static final String PROVIDER_ID = "auth-username-form";
|
||||
public static final UsernameForm SINGLETON = new UsernameForm();
|
||||
|
@ -45,13 +42,6 @@ public class UsernameFormFactory implements AuthenticatorFactory, DisplayTypeAut
|
|||
return SINGLETON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authenticator createDisplay(KeycloakSession session, String displayType) {
|
||||
if (displayType == null) return SINGLETON;
|
||||
if (!OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(displayType)) return null;
|
||||
return ConsoleUsernameAuthenticator.SINGLETON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
|
|
|
@ -18,11 +18,8 @@
|
|||
package org.keycloak.authentication.authenticators.browser;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.AuthenticatorFactory;
|
||||
import org.keycloak.authentication.DisplayTypeAuthenticatorFactory;
|
||||
import org.keycloak.authentication.authenticators.console.ConsoleUsernamePasswordAuthenticator;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
@ -35,7 +32,7 @@ import java.util.List;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class UsernamePasswordFormFactory implements AuthenticatorFactory, DisplayTypeAuthenticatorFactory {
|
||||
public class UsernamePasswordFormFactory implements AuthenticatorFactory {
|
||||
|
||||
public static final String PROVIDER_ID = "auth-username-password-form";
|
||||
public static final UsernamePasswordForm SINGLETON = new UsernamePasswordForm();
|
||||
|
@ -45,13 +42,6 @@ public class UsernamePasswordFormFactory implements AuthenticatorFactory, Displa
|
|||
return SINGLETON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authenticator createDisplay(KeycloakSession session, String displayType) {
|
||||
if (displayType == null) return SINGLETON;
|
||||
if (!OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(displayType)) return null;
|
||||
return ConsoleUsernamePasswordAuthenticator.SINGLETON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
|
|
|
@ -1,115 +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.authentication.authenticators.cli;
|
||||
|
||||
import org.keycloak.authentication.AuthenticationFlowContext;
|
||||
import org.keycloak.authentication.AuthenticationFlowError;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class CliUsernamePasswordAuthenticator extends AbstractUsernameFormAuthenticator implements Authenticator {
|
||||
|
||||
@Override
|
||||
public boolean requiresUser() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authenticate(AuthenticationFlowContext context) {
|
||||
String header = getHeader(context);
|
||||
Response response = context.form()
|
||||
.setStatus(Response.Status.UNAUTHORIZED)
|
||||
.setMediaType(MediaType.TEXT_PLAIN_TYPE)
|
||||
.setResponseHeader(HttpHeaders.WWW_AUTHENTICATE, header)
|
||||
.createForm("cli_splash.ftl");
|
||||
context.challenge(response);
|
||||
|
||||
|
||||
}
|
||||
|
||||
private String getHeader(AuthenticationFlowContext context) {
|
||||
URI callback = getCallbackUrl(context);
|
||||
return "X-Text-Form-Challenge callback=\"" + callback + "\" param=\"username\" label=\"Username: \" mask=false param=\"password\" label=\"Password: \" mask=true";
|
||||
}
|
||||
|
||||
private URI getCallbackUrl(AuthenticationFlowContext context) {
|
||||
return context.getActionUrl(context.generateAccessCode(), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Response challenge(AuthenticationFlowContext context, String error) {
|
||||
String header = getHeader(context);
|
||||
Response response = Response.status(401)
|
||||
.type(MediaType.TEXT_PLAIN_TYPE)
|
||||
.header(HttpHeaders.WWW_AUTHENTICATE, header)
|
||||
.entity("\n" + context.form().getMessage(error) + "\n")
|
||||
.build();
|
||||
return response;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Response setDuplicateUserChallenge(AuthenticationFlowContext context, String eventError, String loginFormError, AuthenticationFlowError authenticatorError) {
|
||||
context.getEvent().error(eventError);
|
||||
String header = getHeader(context);
|
||||
Response challengeResponse = Response.status(401)
|
||||
.type(MediaType.TEXT_PLAIN_TYPE)
|
||||
.header(HttpHeaders.WWW_AUTHENTICATE, header)
|
||||
.entity("\n" + context.form().getMessage(loginFormError) + "\n")
|
||||
.build();
|
||||
|
||||
context.failureChallenge(authenticatorError, challengeResponse);
|
||||
return challengeResponse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void action(AuthenticationFlowContext context) {
|
||||
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
||||
if (!validateUserAndPassword(context, formData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
|
@ -1,103 +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.authentication.authenticators.cli;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.AuthenticatorFactory;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.credential.PasswordCredentialModel;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class CliUsernamePasswordAuthenticatorFactory implements AuthenticatorFactory {
|
||||
|
||||
public static final String PROVIDER_ID = "cli-username-password";
|
||||
public static final CliUsernamePasswordAuthenticator SINGLETON = new CliUsernamePasswordAuthenticator();
|
||||
|
||||
@Override
|
||||
public Authenticator create(KeycloakSession session) {
|
||||
return SINGLETON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReferenceCategory() {
|
||||
return PasswordCredentialModel.TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfigurable() {
|
||||
return false;
|
||||
}
|
||||
public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
|
||||
AuthenticationExecutionModel.Requirement.REQUIRED
|
||||
};
|
||||
|
||||
@Override
|
||||
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
|
||||
return REQUIREMENT_CHOICES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayType() {
|
||||
return "Username Password Challenge";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "Proprietary challenge protocol for CLI clients that queries for username password";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUserSetupAllowed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
|
@ -17,13 +17,11 @@
|
|||
|
||||
package org.keycloak.authentication.authenticators.conditional;
|
||||
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.AuthenticatorFactory;
|
||||
import org.keycloak.authentication.DisplayTypeAuthenticatorFactory;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
public interface ConditionalAuthenticatorFactory extends AuthenticatorFactory, DisplayTypeAuthenticatorFactory {
|
||||
public interface ConditionalAuthenticatorFactory extends AuthenticatorFactory {
|
||||
|
||||
String REFERENCE_CATEGORY = "condition";
|
||||
|
||||
|
@ -32,13 +30,6 @@ public interface ConditionalAuthenticatorFactory extends AuthenticatorFactory, D
|
|||
return getSingleton();
|
||||
}
|
||||
|
||||
@Override
|
||||
default Authenticator createDisplay(KeycloakSession session, String displayType) {
|
||||
if (displayType == null) return create(session);
|
||||
if (!OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(displayType)) return null;
|
||||
return create(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
default String getReferenceCategory() {
|
||||
return REFERENCE_CATEGORY;
|
||||
|
|
|
@ -1,73 +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.authentication.authenticators.console;
|
||||
|
||||
import org.keycloak.authentication.AuthenticationFlowContext;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.ConsoleDisplayMode;
|
||||
import org.keycloak.authentication.authenticators.browser.OTPFormAuthenticator;
|
||||
import org.keycloak.models.credential.OTPCredentialModel;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class ConsoleOTPFormAuthenticator extends OTPFormAuthenticator implements Authenticator {
|
||||
public static final ConsoleOTPFormAuthenticator SINGLETON = new ConsoleOTPFormAuthenticator();
|
||||
|
||||
public static URI getCallbackUrl(AuthenticationFlowContext context) {
|
||||
return context.getActionUrl(context.generateAccessCode(), true);
|
||||
}
|
||||
|
||||
protected ConsoleDisplayMode challenge(AuthenticationFlowContext context) {
|
||||
return ConsoleDisplayMode.challenge(context)
|
||||
.header()
|
||||
.param(OTPCredentialModel.TYPE)
|
||||
.label("console-otp")
|
||||
.challenge();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void action(AuthenticationFlowContext context) {
|
||||
validateOTP(context);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void authenticate(AuthenticationFlowContext context) {
|
||||
Response challengeResponse = challenge(context, null);
|
||||
context.challenge(challengeResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Response challenge(AuthenticationFlowContext context, String msg) {
|
||||
if (msg == null) {
|
||||
return challenge(context).response();
|
||||
}
|
||||
return challenge(context).message(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
package org.keycloak.authentication.authenticators.console;
|
||||
|
||||
import org.keycloak.authentication.AuthenticationFlowContext;
|
||||
import org.keycloak.authentication.ConsoleDisplayMode;
|
||||
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
|
||||
public final class ConsolePasswordAuthenticator extends ConsoleUsernamePasswordAuthenticator {
|
||||
|
||||
public static ConsolePasswordAuthenticator SINGLETON = new ConsolePasswordAuthenticator();
|
||||
|
||||
@Override
|
||||
protected ConsoleDisplayMode challenge(AuthenticationFlowContext context) {
|
||||
return ConsoleDisplayMode.challenge(context)
|
||||
.header()
|
||||
.param("password")
|
||||
.label("console-password")
|
||||
.mask(true)
|
||||
.challenge();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void action(AuthenticationFlowContext context) {
|
||||
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
||||
if (!validatePassword(context, context.getUser(), formData, false)) {
|
||||
return;
|
||||
}
|
||||
context.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresUser() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
package org.keycloak.authentication.authenticators.console;
|
||||
|
||||
import org.keycloak.authentication.AuthenticationFlowContext;
|
||||
import org.keycloak.authentication.ConsoleDisplayMode;
|
||||
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
|
||||
public final class ConsoleUsernameAuthenticator extends ConsoleUsernamePasswordAuthenticator {
|
||||
public static ConsoleUsernameAuthenticator SINGLETON = new ConsoleUsernameAuthenticator();
|
||||
|
||||
@Override
|
||||
protected ConsoleDisplayMode challenge(AuthenticationFlowContext context) {
|
||||
return ConsoleDisplayMode.challenge(context)
|
||||
.header()
|
||||
.param("username")
|
||||
.label("console-username")
|
||||
.mask(true)
|
||||
.challenge();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void action(AuthenticationFlowContext context) {
|
||||
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
||||
if (!validateUser(context, formData)) {
|
||||
return;
|
||||
}
|
||||
context.success();
|
||||
}
|
||||
}
|
|
@ -1,95 +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.authentication.authenticators.console;
|
||||
|
||||
import org.keycloak.authentication.*;
|
||||
import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class ConsoleUsernamePasswordAuthenticator extends AbstractUsernameFormAuthenticator implements Authenticator {
|
||||
|
||||
public static final ConsoleUsernamePasswordAuthenticator SINGLETON = new ConsoleUsernamePasswordAuthenticator();
|
||||
|
||||
@Override
|
||||
public boolean requiresUser() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected ConsoleDisplayMode challenge(AuthenticationFlowContext context) {
|
||||
return ConsoleDisplayMode.challenge(context)
|
||||
.header()
|
||||
.param("username")
|
||||
.label("console-username")
|
||||
.param("password")
|
||||
.label("console-password")
|
||||
.mask(true)
|
||||
.challenge();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authenticate(AuthenticationFlowContext context) {
|
||||
Response response = challenge(context).form().createForm("cli_splash.ftl");
|
||||
context.challenge(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Response challenge(AuthenticationFlowContext context, String error) {
|
||||
return challenge(context).message(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Response setDuplicateUserChallenge(AuthenticationFlowContext context, String eventError, String loginFormError, AuthenticationFlowError authenticatorError) {
|
||||
context.getEvent().error(eventError);
|
||||
Response response = challenge(context).message(loginFormError);
|
||||
|
||||
context.failureChallenge(authenticatorError, response);
|
||||
return response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void action(AuthenticationFlowContext context) {
|
||||
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
||||
if (!validateUserAndPassword(context, formData)) {
|
||||
return;
|
||||
}
|
||||
context.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
|
@ -1,102 +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.authentication.authenticators.console;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.AuthenticatorFactory;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.credential.PasswordCredentialModel;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class ConsoleUsernamePasswordAuthenticatorFactory implements AuthenticatorFactory {
|
||||
|
||||
public static final String PROVIDER_ID = "console-username-password";
|
||||
|
||||
@Override
|
||||
public Authenticator create(KeycloakSession session) {
|
||||
return ConsoleUsernamePasswordAuthenticator.SINGLETON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReferenceCategory() {
|
||||
return PasswordCredentialModel.TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfigurable() {
|
||||
return false;
|
||||
}
|
||||
public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
|
||||
AuthenticationExecutionModel.Requirement.REQUIRED
|
||||
};
|
||||
|
||||
@Override
|
||||
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
|
||||
return REQUIREMENT_CHOICES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayType() {
|
||||
return "Username Password Challenge";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "Proprietary challenge protocol for CLI clients that queries for username password";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUserSetupAllowed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,73 +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.authentication.requiredactions;
|
||||
|
||||
import org.keycloak.authentication.RequiredActionContext;
|
||||
import org.keycloak.authentication.RequiredActionProvider;
|
||||
import org.keycloak.authentication.ConsoleDisplayMode;
|
||||
import org.keycloak.common.util.Time;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class ConsoleTermsAndConditions implements RequiredActionProvider {
|
||||
public static final ConsoleTermsAndConditions SINGLETON = new ConsoleTermsAndConditions();
|
||||
public static final String USER_ATTRIBUTE = TermsAndConditions.PROVIDER_ID;
|
||||
|
||||
@Override
|
||||
public void evaluateTriggers(RequiredActionContext context) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void requiredActionChallenge(RequiredActionContext context) {
|
||||
Response challenge = ConsoleDisplayMode.challenge(context)
|
||||
.header()
|
||||
.param("accept")
|
||||
.label("console-accept-terms")
|
||||
.message("termsPlainText");
|
||||
context.challenge(challenge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processAction(RequiredActionContext context) {
|
||||
String accept = context.getHttpRequest().getDecodedFormParameters().getFirst("accept");
|
||||
|
||||
String yes = context.form().getMessage("console-accept");
|
||||
|
||||
if (!accept.equals(yes)) {
|
||||
context.getUser().removeAttribute(USER_ATTRIBUTE);
|
||||
requiredActionChallenge(context);
|
||||
return;
|
||||
}
|
||||
|
||||
context.getUser().setAttribute(USER_ATTRIBUTE, Arrays.asList(Integer.toString(Time.currentTime())));
|
||||
|
||||
context.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
|
@ -1,98 +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.authentication.requiredactions;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.authentication.*;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.models.*;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.services.validation.Validation;
|
||||
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class ConsoleUpdatePassword extends UpdatePassword implements RequiredActionProvider {
|
||||
public static final ConsoleUpdatePassword SINGLETON = new ConsoleUpdatePassword();
|
||||
|
||||
private static final Logger logger = Logger.getLogger(ConsoleUpdatePassword.class);
|
||||
public static final String PASSWORD_NEW = "password-new";
|
||||
public static final String PASSWORD_CONFIRM = "password-confirm";
|
||||
|
||||
protected ConsoleDisplayMode challenge(RequiredActionContext context) {
|
||||
return ConsoleDisplayMode.challenge(context)
|
||||
.header()
|
||||
.param(PASSWORD_NEW)
|
||||
.label("console-new-password")
|
||||
.mask(true)
|
||||
.param(PASSWORD_CONFIRM)
|
||||
.label("console-confirm-password")
|
||||
.mask(true)
|
||||
.challenge();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void requiredActionChallenge(RequiredActionContext context) {
|
||||
context.challenge(
|
||||
challenge(context).message("console-update-password"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processAction(RequiredActionContext context) {
|
||||
EventBuilder event = context.getEvent();
|
||||
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
||||
event.event(EventType.UPDATE_PASSWORD);
|
||||
String passwordNew = formData.getFirst(PASSWORD_NEW);
|
||||
String passwordConfirm = formData.getFirst(PASSWORD_CONFIRM);
|
||||
|
||||
EventBuilder errorEvent = event.clone().event(EventType.UPDATE_PASSWORD_ERROR)
|
||||
.client(context.getAuthenticationSession().getClient())
|
||||
.user(context.getAuthenticationSession().getAuthenticatedUser());
|
||||
|
||||
if (Validation.isBlank(passwordNew)) {
|
||||
context.challenge(challenge(context).message(Messages.MISSING_PASSWORD));
|
||||
errorEvent.error(Errors.PASSWORD_MISSING);
|
||||
return;
|
||||
} else if (!passwordNew.equals(passwordConfirm)) {
|
||||
context.challenge(challenge(context).message(Messages.NOTMATCH_PASSWORD));
|
||||
errorEvent.error(Errors.PASSWORD_CONFIRM_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
context.getUser().credentialManager().updateCredential(UserCredentialModel.password(passwordNew, false));
|
||||
context.success();
|
||||
} catch (ModelException me) {
|
||||
errorEvent.detail(Details.REASON, me.getMessage()).error(Errors.PASSWORD_REJECTED);
|
||||
context.challenge(challenge(context).text(me.getMessage()));
|
||||
return;
|
||||
} catch (Exception ape) {
|
||||
errorEvent.detail(Details.REASON, ape.getMessage()).error(Errors.PASSWORD_REJECTED);
|
||||
context.challenge(challenge(context).text(ape.getMessage()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,51 +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.authentication.requiredactions;
|
||||
|
||||
import org.keycloak.authentication.RequiredActionContext;
|
||||
import org.keycloak.authentication.RequiredActionProvider;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class ConsoleUpdateProfile implements RequiredActionProvider {
|
||||
public static final ConsoleUpdateProfile SINGLETON = new ConsoleUpdateProfile();
|
||||
|
||||
@Override
|
||||
public void evaluateTriggers(RequiredActionContext context) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requiredActionChallenge(RequiredActionContext context) {
|
||||
// do nothing right now. I think this behavior is ok. We just defer this action until a browser login happens.
|
||||
context.ignore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processAction(RequiredActionContext context) {
|
||||
throw new RuntimeException("Should be unreachable");
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
|
@ -1,102 +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.authentication.requiredactions;
|
||||
|
||||
import org.keycloak.authentication.RequiredActionContext;
|
||||
import org.keycloak.authentication.RequiredActionProvider;
|
||||
import org.keycloak.authentication.ConsoleDisplayMode;
|
||||
import org.keycloak.credential.CredentialModel;
|
||||
import org.keycloak.credential.CredentialProvider;
|
||||
import org.keycloak.credential.OTPCredentialProvider;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.forms.login.freemarker.model.TotpBean;
|
||||
import org.keycloak.models.OTPPolicy;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.credential.OTPCredentialModel;
|
||||
import org.keycloak.models.utils.CredentialValidation;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.services.validation.Validation;
|
||||
import org.keycloak.utils.CredentialHelper;
|
||||
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class ConsoleUpdateTotp implements RequiredActionProvider {
|
||||
public static final ConsoleUpdateTotp SINGLETON = new ConsoleUpdateTotp();
|
||||
|
||||
@Override
|
||||
public void evaluateTriggers(RequiredActionContext context) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requiredActionChallenge(RequiredActionContext context) {
|
||||
TotpBean totpBean = new TotpBean(context.getSession(), context.getRealm(), context.getUser(), context.getUriInfo().getRequestUriBuilder());
|
||||
String totpSecret = totpBean.getTotpSecret();
|
||||
context.getAuthenticationSession().setAuthNote("totpSecret", totpSecret);
|
||||
Response challenge = challenge(context).form()
|
||||
.setAttribute("totp", totpBean)
|
||||
.createForm("login-config-totp-text.ftl");
|
||||
context.challenge(challenge);
|
||||
}
|
||||
|
||||
protected ConsoleDisplayMode challenge(RequiredActionContext context) {
|
||||
return ConsoleDisplayMode.challenge(context)
|
||||
.header()
|
||||
.param("totp")
|
||||
.label("console-otp")
|
||||
.challenge();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processAction(RequiredActionContext context) {
|
||||
EventBuilder event = context.getEvent();
|
||||
event.event(EventType.UPDATE_TOTP);
|
||||
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
||||
String challengeResponse = formData.getFirst("totp");
|
||||
String totpSecret = context.getAuthenticationSession().getAuthNote("totpSecret");
|
||||
String userLabel = formData.getFirst("userLabel");
|
||||
|
||||
OTPPolicy policy = context.getRealm().getOTPPolicy();
|
||||
OTPCredentialModel credentialModel = OTPCredentialModel.createFromPolicy(context.getRealm(), totpSecret, userLabel);
|
||||
if (Validation.isBlank(challengeResponse)) {
|
||||
context.challenge(challenge(context).message(Messages.MISSING_TOTP));
|
||||
return;
|
||||
} else if (!CredentialValidation.validOTP(challengeResponse, credentialModel, policy.getLookAheadWindow())) {
|
||||
context.challenge(challenge(context).message(Messages.INVALID_TOTP));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CredentialHelper.createOTPCredential(context.getSession(), context.getRealm(), context.getUser(), challengeResponse, credentialModel)) {
|
||||
context.challenge(challenge(context).message(Messages.INVALID_TOTP));
|
||||
return;
|
||||
}
|
||||
context.getAuthenticationSession().removeAuthNote("totpSecret");
|
||||
context.success();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
|
@ -1,141 +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.authentication.requiredactions;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.authentication.RequiredActionContext;
|
||||
import org.keycloak.authentication.RequiredActionProvider;
|
||||
import org.keycloak.authentication.ConsoleDisplayMode;
|
||||
import org.keycloak.common.util.SecretGenerator;
|
||||
import org.keycloak.email.EmailException;
|
||||
import org.keycloak.email.EmailTemplateProvider;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.models.*;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.services.validation.Validation;
|
||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||
|
||||
import javax.ws.rs.core.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class ConsoleVerifyEmail implements RequiredActionProvider {
|
||||
public static final ConsoleVerifyEmail SINGLETON = new ConsoleVerifyEmail();
|
||||
private static final Logger logger = Logger.getLogger(ConsoleVerifyEmail.class);
|
||||
@Override
|
||||
public void evaluateTriggers(RequiredActionContext context) {
|
||||
if (context.getRealm().isVerifyEmail() && !context.getUser().isEmailVerified()) {
|
||||
context.getUser().addRequiredAction(UserModel.RequiredAction.VERIFY_EMAIL);
|
||||
logger.debug("User is required to verify email");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requiredActionChallenge(RequiredActionContext context) {
|
||||
AuthenticationSessionModel authSession = context.getAuthenticationSession();
|
||||
|
||||
if (context.getUser().isEmailVerified()) {
|
||||
context.success();
|
||||
authSession.removeAuthNote(Constants.VERIFY_EMAIL_KEY);
|
||||
return;
|
||||
}
|
||||
|
||||
String email = context.getUser().getEmail();
|
||||
if (Validation.isBlank(email)) {
|
||||
context.ignore();
|
||||
return;
|
||||
}
|
||||
|
||||
Response challenge = sendVerifyEmail(context);
|
||||
context.challenge(challenge);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void processAction(RequiredActionContext context) {
|
||||
EventBuilder event = context.getEvent().clone().event(EventType.VERIFY_EMAIL).detail(Details.EMAIL, context.getUser().getEmail());
|
||||
String code = context.getAuthenticationSession().getAuthNote(Constants.VERIFY_EMAIL_CODE);
|
||||
if (code == null) {
|
||||
requiredActionChallenge(context);
|
||||
return;
|
||||
}
|
||||
|
||||
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
||||
String emailCode = formData.getFirst(EMAIL_CODE);
|
||||
|
||||
if (!code.equals(emailCode)) {
|
||||
context.challenge(
|
||||
challenge(context).message(Messages.INVALID_CODE)
|
||||
);
|
||||
event.error(Errors.INVALID_CODE);
|
||||
return;
|
||||
}
|
||||
event.success();
|
||||
context.success();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
public static String EMAIL_CODE="email_code";
|
||||
protected ConsoleDisplayMode challenge(RequiredActionContext context) {
|
||||
return ConsoleDisplayMode.challenge(context)
|
||||
.header()
|
||||
.param(EMAIL_CODE)
|
||||
.label("console-email-code")
|
||||
.challenge();
|
||||
}
|
||||
|
||||
private Response sendVerifyEmail(RequiredActionContext context) throws UriBuilderException, IllegalArgumentException {
|
||||
KeycloakSession session = context.getSession();
|
||||
UserModel user = context.getUser();
|
||||
AuthenticationSessionModel authSession = context.getAuthenticationSession();
|
||||
EventBuilder event = context.getEvent().clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail());
|
||||
String code = SecretGenerator.getInstance().randomString(8);
|
||||
authSession.setAuthNote(Constants.VERIFY_EMAIL_CODE, code);
|
||||
RealmModel realm = session.getContext().getRealm();
|
||||
|
||||
Map<String, Object> attributes = new HashMap<>();
|
||||
attributes.put("code", code);
|
||||
|
||||
try {
|
||||
session
|
||||
.getProvider(EmailTemplateProvider.class)
|
||||
.setAuthenticationSession(authSession)
|
||||
.setRealm(realm)
|
||||
.setUser(user)
|
||||
.send("emailVerificationSubject", "email-verification-with-code.ftl", attributes);
|
||||
event.success();
|
||||
} catch (EmailException e) {
|
||||
logger.error("Failed to send verification email", e);
|
||||
event.error(Errors.EMAIL_SEND_FAILED);
|
||||
}
|
||||
|
||||
return challenge(context).text(context.form().getMessage("console-verify-email", user.getEmail()));
|
||||
}
|
||||
}
|
|
@ -18,7 +18,6 @@
|
|||
package org.keycloak.authentication.requiredactions;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.authentication.*;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
@ -31,7 +30,7 @@ import java.util.Arrays;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class TermsAndConditions implements RequiredActionProvider, RequiredActionFactory, DisplayTypeRequiredActionFactory {
|
||||
public class TermsAndConditions implements RequiredActionProvider, RequiredActionFactory {
|
||||
public static final String PROVIDER_ID = "terms_and_conditions";
|
||||
public static final String USER_ATTRIBUTE = PROVIDER_ID;
|
||||
|
||||
|
@ -40,15 +39,6 @@ public class TermsAndConditions implements RequiredActionProvider, RequiredActio
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequiredActionProvider createDisplay(KeycloakSession session, String displayType) {
|
||||
if (displayType == null) return this;
|
||||
if (!OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(displayType)) return null;
|
||||
return ConsoleTermsAndConditions.SINGLETON;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ package org.keycloak.authentication.requiredactions;
|
|||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.authentication.*;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.credential.CredentialModel;
|
||||
|
@ -53,7 +52,7 @@ import java.util.stream.Collectors;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class UpdatePassword implements RequiredActionProvider, RequiredActionFactory, DisplayTypeRequiredActionFactory {
|
||||
public class UpdatePassword implements RequiredActionProvider, RequiredActionFactory {
|
||||
private static final Logger logger = Logger.getLogger(UpdatePassword.class);
|
||||
|
||||
@Override
|
||||
|
@ -168,15 +167,6 @@ public class UpdatePassword implements RequiredActionProvider, RequiredActionFac
|
|||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RequiredActionProvider createDisplay(KeycloakSession session, String displayType) {
|
||||
if (displayType == null) return this;
|
||||
if (!OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(displayType)) return null;
|
||||
return ConsoleUpdatePassword.SINGLETON;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
|
|
|
@ -18,8 +18,6 @@
|
|||
package org.keycloak.authentication.requiredactions;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.authentication.DisplayTypeRequiredActionFactory;
|
||||
import org.keycloak.authentication.InitiatedActionSupport;
|
||||
import org.keycloak.authentication.RequiredActionContext;
|
||||
import org.keycloak.authentication.RequiredActionFactory;
|
||||
|
@ -47,7 +45,7 @@ import java.util.List;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class UpdateProfile implements RequiredActionProvider, RequiredActionFactory, DisplayTypeRequiredActionFactory {
|
||||
public class UpdateProfile implements RequiredActionProvider, RequiredActionFactory {
|
||||
@Override
|
||||
public InitiatedActionSupport initiatedActionSupport() {
|
||||
return InitiatedActionSupport.SUPPORTED;
|
||||
|
@ -113,16 +111,6 @@ public class UpdateProfile implements RequiredActionProvider, RequiredActionFact
|
|||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RequiredActionProvider createDisplay(KeycloakSession session, String displayType) {
|
||||
if (displayType == null) return this;
|
||||
if (!OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(displayType)) return null;
|
||||
return ConsoleUpdateProfile.SINGLETON;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
|
|
|
@ -18,9 +18,7 @@
|
|||
package org.keycloak.authentication.requiredactions;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.authentication.CredentialRegistrator;
|
||||
import org.keycloak.authentication.DisplayTypeRequiredActionFactory;
|
||||
import org.keycloak.authentication.InitiatedActionSupport;
|
||||
import org.keycloak.authentication.RequiredActionContext;
|
||||
import org.keycloak.authentication.RequiredActionFactory;
|
||||
|
@ -49,7 +47,7 @@ import java.util.stream.Stream;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class UpdateTotp implements RequiredActionProvider, RequiredActionFactory, DisplayTypeRequiredActionFactory, CredentialRegistrator {
|
||||
public class UpdateTotp implements RequiredActionProvider, RequiredActionFactory, CredentialRegistrator {
|
||||
@Override
|
||||
public InitiatedActionSupport initiatedActionSupport() {
|
||||
return InitiatedActionSupport.SUPPORTED;
|
||||
|
@ -135,15 +133,6 @@ public class UpdateTotp implements RequiredActionProvider, RequiredActionFactory
|
|||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RequiredActionProvider createDisplay(KeycloakSession session, String displayType) {
|
||||
if (displayType == null) return this;
|
||||
if (!OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(displayType)) return null;
|
||||
return ConsoleUpdateTotp.SINGLETON;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ package org.keycloak.authentication.requiredactions;
|
|||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.authentication.*;
|
||||
import org.keycloak.authentication.actiontoken.verifyemail.VerifyEmailActionToken;
|
||||
import org.keycloak.common.util.Time;
|
||||
|
@ -45,7 +44,7 @@ import javax.ws.rs.core.*;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class VerifyEmail implements RequiredActionProvider, RequiredActionFactory, DisplayTypeRequiredActionFactory {
|
||||
public class VerifyEmail implements RequiredActionProvider, RequiredActionFactory {
|
||||
private static final Logger logger = Logger.getLogger(VerifyEmail.class);
|
||||
@Override
|
||||
public void evaluateTriggers(RequiredActionContext context) {
|
||||
|
@ -108,14 +107,6 @@ public class VerifyEmail implements RequiredActionProvider, RequiredActionFactor
|
|||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RequiredActionProvider createDisplay(KeycloakSession session, String displayType) {
|
||||
if (displayType == null) return this;
|
||||
if (!OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(displayType)) return null;
|
||||
return ConsoleVerifyEmail.SINGLETON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
|
|
|
@ -17,9 +17,7 @@
|
|||
package org.keycloak.authentication.requiredactions;
|
||||
|
||||
import com.webauthn4j.validator.attestation.trustworthiness.certpath.CertPathTrustworthinessValidator;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.Config.Scope;
|
||||
import org.keycloak.authentication.DisplayTypeRequiredActionFactory;
|
||||
import org.keycloak.authentication.RequiredActionFactory;
|
||||
import org.keycloak.authentication.RequiredActionProvider;
|
||||
import org.keycloak.common.Profile;
|
||||
|
@ -33,7 +31,7 @@ import com.webauthn4j.anchor.TrustAnchorsResolverImpl;
|
|||
import com.webauthn4j.validator.attestation.trustworthiness.certpath.NullCertPathTrustworthinessValidator;
|
||||
import com.webauthn4j.validator.attestation.trustworthiness.certpath.TrustAnchorCertPathTrustworthinessValidator;
|
||||
|
||||
public class WebAuthnRegisterFactory implements RequiredActionFactory, DisplayTypeRequiredActionFactory, EnvironmentDependentProviderFactory {
|
||||
public class WebAuthnRegisterFactory implements RequiredActionFactory, EnvironmentDependentProviderFactory {
|
||||
|
||||
public static final String PROVIDER_ID = "webauthn-register";
|
||||
|
||||
|
@ -77,14 +75,6 @@ public class WebAuthnRegisterFactory implements RequiredActionFactory, DisplayTy
|
|||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequiredActionProvider createDisplay(KeycloakSession session, String displayType) {
|
||||
if (displayType == null) return create(session);
|
||||
if (!OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(displayType)) return null;
|
||||
// TODO : write console typed provider?
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayText() {
|
||||
return "Webauthn Register";
|
||||
|
|
|
@ -103,7 +103,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
|
||||
protected String accessCode;
|
||||
protected Response.Status status;
|
||||
protected javax.ws.rs.core.MediaType contentType;
|
||||
protected List<AuthorizationDetails> clientScopesRequested;
|
||||
protected Map<String, String> httpResponseHeaders = new HashMap<>();
|
||||
protected URI actionUri;
|
||||
|
@ -435,25 +434,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
return formatMessage(msg, messagesBundle, locale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage(String message, String... parameters) {
|
||||
Theme theme;
|
||||
try {
|
||||
theme = getTheme();
|
||||
} catch (IOException e) {
|
||||
logger.error("Failed to create theme", e);
|
||||
throw new RuntimeException("Failed to create theme");
|
||||
}
|
||||
|
||||
Locale locale = session.getContext().resolveLocale(user);
|
||||
Properties messagesBundle = handleThemeResources(theme, locale);
|
||||
Map<String, String> localizationTexts = realm.getRealmLocalizationTextsByLocale(locale.getCountry());
|
||||
messagesBundle.putAll(localizationTexts);
|
||||
FormMessage msg = new FormMessage(message, (Object[]) parameters);
|
||||
return formatMessage(msg, messagesBundle, locale);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create common attributes used in all templates.
|
||||
*
|
||||
|
@ -542,8 +522,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
protected Response processTemplate(Theme theme, String templateName, Locale locale) {
|
||||
try {
|
||||
String result = freeMarker.processTemplate(attributes, templateName, theme);
|
||||
javax.ws.rs.core.MediaType mediaType = contentType == null ? MediaType.TEXT_HTML_UTF_8_TYPE : contentType;
|
||||
Response.ResponseBuilder builder = Response.status(status == null ? Response.Status.OK : status).type(mediaType).language(locale).entity(result);
|
||||
Response.ResponseBuilder builder = Response.status(status == null ? Response.Status.OK : status).type(MediaType.TEXT_HTML_UTF_8_TYPE).language(locale).entity(result);
|
||||
for (Map.Entry<String, String> entry : httpResponseHeaders.entrySet()) {
|
||||
builder.header(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
@ -826,12 +805,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoginFormsProvider setMediaType(javax.ws.rs.core.MediaType type) {
|
||||
this.contentType = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoginFormsProvider setActionUri(URI actionUri) {
|
||||
this.actionUri = actionUri;
|
||||
|
|
|
@ -22,12 +22,9 @@ import org.keycloak.OAuth2Constants;
|
|||
import org.keycloak.TokenVerifier;
|
||||
import org.keycloak.TokenVerifier.Predicate;
|
||||
import org.keycloak.TokenVerifier.TokenTypeCheck;
|
||||
import org.keycloak.authentication.AuthenticationFlowError;
|
||||
import org.keycloak.authentication.AuthenticationFlowException;
|
||||
import org.keycloak.authentication.AuthenticationProcessor;
|
||||
import org.keycloak.authentication.AuthenticatorUtil;
|
||||
import org.keycloak.authentication.ConsoleDisplayMode;
|
||||
import org.keycloak.authentication.DisplayTypeRequiredActionFactory;
|
||||
import org.keycloak.authentication.InitiatedActionSupport;
|
||||
import org.keycloak.authentication.RequiredActionContext;
|
||||
import org.keycloak.authentication.RequiredActionContextResult;
|
||||
|
@ -1249,23 +1246,8 @@ public class AuthenticationManager {
|
|||
}
|
||||
|
||||
public static RequiredActionProvider createRequiredAction(RequiredActionContextResult context) {
|
||||
String display = context.getAuthenticationSession().getAuthNote(OAuth2Constants.DISPLAY);
|
||||
if (display == null) return context.getFactory().create(context.getSession());
|
||||
|
||||
|
||||
if (context.getFactory() instanceof DisplayTypeRequiredActionFactory) {
|
||||
RequiredActionProvider provider = ((DisplayTypeRequiredActionFactory)context.getFactory()).createDisplay(context.getSession(), display);
|
||||
if (provider != null) return provider;
|
||||
}
|
||||
// todo create a provider for handling lack of display support
|
||||
if (OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(display)) {
|
||||
context.getAuthenticationSession().removeAuthNote(OAuth2Constants.DISPLAY);
|
||||
throw new AuthenticationFlowException(AuthenticationFlowError.DISPLAY_NOT_SUPPORTED, ConsoleDisplayMode.browserContinue(context.getSession(), context.getUriInfo().getRequestUri().toString()));
|
||||
|
||||
} else {
|
||||
return context.getFactory().create(context.getSession());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected static Response executionActions(KeycloakSession session, AuthenticationSessionModel authSession,
|
||||
|
|
|
@ -45,12 +45,6 @@ public class TotpUtils {
|
|||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String decode(String totpSecretEncoded) {
|
||||
String encoded = totpSecretEncoded.replace(" ", "");
|
||||
byte[] bytes = Base32.decode(encoded);
|
||||
return new String(bytes);
|
||||
}
|
||||
|
||||
public static String qrCode(String totpSecret, RealmModel realm, UserModel user) {
|
||||
try {
|
||||
String keyUri = realm.getOTPPolicy().getKeyURI(realm, user, totpSecret);
|
||||
|
|
|
@ -45,7 +45,6 @@ org.keycloak.protocol.saml.profile.ecp.authenticator.HttpBasicAuthenticatorFacto
|
|||
org.keycloak.authentication.authenticators.x509.X509ClientCertificateAuthenticatorFactory
|
||||
org.keycloak.authentication.authenticators.x509.ValidateX509CertificateUsernameFactory
|
||||
org.keycloak.protocol.docker.DockerAuthenticatorFactory
|
||||
org.keycloak.authentication.authenticators.console.ConsoleUsernamePasswordAuthenticatorFactory
|
||||
org.keycloak.authentication.authenticators.challenge.BasicAuthAuthenticatorFactory
|
||||
org.keycloak.authentication.authenticators.challenge.BasicAuthOTPAuthenticatorFactory
|
||||
org.keycloak.authentication.authenticators.challenge.NoCookieFlowRedirectAuthenticatorFactory
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
package org.keycloak.testsuite.authentication;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.AuthenticatorFactory;
|
||||
import org.keycloak.authentication.DisplayTypeAuthenticatorFactory;
|
||||
import org.keycloak.authentication.authenticators.AttemptedAuthenticator;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
@ -15,7 +12,7 @@ import java.util.Arrays;
|
|||
import java.util.List;
|
||||
|
||||
|
||||
public class SetUserAttributeAuthenticatorFactory implements AuthenticatorFactory, DisplayTypeAuthenticatorFactory {
|
||||
public class SetUserAttributeAuthenticatorFactory implements AuthenticatorFactory {
|
||||
|
||||
public static final String PROVIDER_ID = "set-attribute";
|
||||
|
||||
|
@ -25,13 +22,6 @@ public class SetUserAttributeAuthenticatorFactory implements AuthenticatorFactor
|
|||
AuthenticationExecutionModel.Requirement.REQUIRED,
|
||||
AuthenticationExecutionModel.Requirement.DISABLED};
|
||||
|
||||
@Override
|
||||
public Authenticator createDisplay(KeycloakSession keycloakSession, String displayType) {
|
||||
if (displayType == null) return create(keycloakSession);
|
||||
if (!OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(displayType)) return null;
|
||||
return AttemptedAuthenticator.SINGLETON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReferenceCategory() {
|
||||
return null;
|
||||
|
|
|
@ -156,8 +156,6 @@ public class ProvidersTest extends AbstractAuthenticationTest {
|
|||
"Validates username and password from X509 client certificate received as a part of mutual SSL handshake.");
|
||||
addProviderInfo(result, "basic-auth", "Basic Auth Challenge", "Challenge-response authentication using HTTP BASIC scheme.");
|
||||
addProviderInfo(result, "basic-auth-otp", "Basic Auth Password+OTP", "Challenge-response authentication using HTTP BASIC scheme. Password param should contain a combination of password + otp. Realm's OTP policy is used to determine how to parse this. This SHOULD NOT BE USED in conjection with regular basic auth provider.");
|
||||
addProviderInfo(result, "console-username-password", "Username Password Challenge",
|
||||
"Proprietary challenge protocol for CLI clients that queries for username password");
|
||||
addProviderInfo(result, "direct-grant-auth-x509-username", "X509/Validate Username",
|
||||
"Validates username and password from X509 client certificate received as a part of mutual SSL handshake.");
|
||||
addProviderInfo(result, "direct-grant-validate-otp", "OTP", "Validates the one time password supplied as a 'totp' form parameter in direct grant request");
|
||||
|
|
|
@ -1,198 +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.forms;
|
||||
|
||||
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.authentication.authenticators.console.ConsoleUsernamePasswordAuthenticatorFactory;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.AuthenticationFlowBindings;
|
||||
import org.keycloak.models.AuthenticationFlowModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
import org.keycloak.testsuite.pages.ErrorPage;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
|
||||
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.HttpHeaders;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
|
||||
import org.keycloak.testsuite.util.AdminClientUtil;
|
||||
|
||||
/**
|
||||
* Test that clients can override auth flows
|
||||
*
|
||||
* @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
|
||||
*/
|
||||
@AuthServerContainerExclude(AuthServer.REMOTE)
|
||||
public class ChallengeFlowTest extends AbstractTestRealmKeycloakTest {
|
||||
|
||||
public static final String TEST_APP_DIRECT_OVERRIDE = "test-app-direct-override";
|
||||
public static final String TEST_APP_FLOW = "test-app-flow";
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(this);
|
||||
|
||||
@Page
|
||||
protected AppPage appPage;
|
||||
|
||||
@Page
|
||||
protected LoginPage loginPage;
|
||||
|
||||
@Page
|
||||
protected ErrorPage errorPage;
|
||||
|
||||
@Override
|
||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setupFlows() {
|
||||
SerializableApplicationData serializedApplicationData = new SerializableApplicationData(oauth.APP_AUTH_ROOT, oauth.APP_ROOT + "/admin", oauth.APP_AUTH_ROOT + "/*");
|
||||
|
||||
testingClient.server().run(session -> {
|
||||
RealmModel realm = session.realms().getRealmByName("test");
|
||||
|
||||
ClientModel client = session.clients().getClientByClientId(realm, "test-app-flow");
|
||||
if (client != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Parent flow
|
||||
AuthenticationFlowModel browser = new AuthenticationFlowModel();
|
||||
browser.setAlias("cli-challenge");
|
||||
browser.setDescription("challenge based authentication");
|
||||
browser.setProviderId("basic-flow");
|
||||
browser.setTopLevel(true);
|
||||
browser.setBuiltIn(true);
|
||||
browser = realm.addAuthenticationFlow(browser);
|
||||
|
||||
// Subflow2 - push the button
|
||||
AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
|
||||
execution.setParentFlow(browser.getId());
|
||||
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
|
||||
execution.setAuthenticator(ConsoleUsernamePasswordAuthenticatorFactory.PROVIDER_ID);
|
||||
execution.setPriority(10);
|
||||
execution.setAuthenticatorFlow(false);
|
||||
realm.addAuthenticatorExecution(execution);
|
||||
|
||||
client = realm.addClient(TEST_APP_FLOW);
|
||||
client.setSecret("password");
|
||||
client.setBaseUrl(serializedApplicationData.applicationBaseUrl);
|
||||
client.setManagementUrl(serializedApplicationData.applicationManagementUrl);
|
||||
client.setEnabled(true);
|
||||
client.addRedirectUri(serializedApplicationData.applicationRedirectUrl);
|
||||
client.addRedirectUri("urn:ietf:wg:oauth:2.0:oob");
|
||||
client.setAuthenticationFlowBindingOverride(AuthenticationFlowBindings.BROWSER_BINDING, browser.getId());
|
||||
client.setPublicClient(false);
|
||||
});
|
||||
}
|
||||
|
||||
//@Test
|
||||
public void testRunConsole() throws Exception {
|
||||
Thread.sleep(10000000);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testChallengeFlow() throws Exception {
|
||||
oauth.clientId(TEST_APP_FLOW);
|
||||
String loginFormUrl = oauth.getLoginFormUrl();
|
||||
Client client = AdminClientUtil.createResteasyClient();
|
||||
WebTarget loginTarget = client.target(loginFormUrl);
|
||||
Response response = loginTarget.request().get();
|
||||
Assert.assertEquals(401, response.getStatus());
|
||||
String authenticateHeader = response.getHeaderString(HttpHeaders.WWW_AUTHENTICATE);
|
||||
Assert.assertNotNull(authenticateHeader);
|
||||
//System.out.println(authenticateHeader);
|
||||
String splash = response.readEntity(String.class);
|
||||
//System.out.println(splash);
|
||||
response.close();
|
||||
|
||||
// respin Client to make absolutely sure no cookie caching. need to test that it works with null auth_session_id cookie.
|
||||
client.close();
|
||||
client = AdminClientUtil.createResteasyClient();
|
||||
|
||||
|
||||
authenticateHeader = authenticateHeader.trim();
|
||||
Pattern callbackPattern = Pattern.compile("callback\\s*=\\s*\"([^\"]+)\"");
|
||||
Pattern paramPattern = Pattern.compile("param=\"([^\"]+)\"\\s+label=\"([^\"]+)\"");
|
||||
Matcher m = callbackPattern.matcher(authenticateHeader);
|
||||
String callback = null;
|
||||
if (m.find()) {
|
||||
callback = m.group(1);
|
||||
//System.out.println("------");
|
||||
//System.out.println("callback:");
|
||||
//System.out.println(" " + callback);
|
||||
}
|
||||
m = paramPattern.matcher(authenticateHeader);
|
||||
List<String> params = new LinkedList<>();
|
||||
List<String> labels = new LinkedList<>();
|
||||
while (m.find()) {
|
||||
String param = m.group(1);
|
||||
String label = m.group(2);
|
||||
params.add(param);
|
||||
labels.add(label);
|
||||
//System.out.println("------");
|
||||
//System.out.println("param:" + param);
|
||||
//System.out.println("label:" + label);
|
||||
}
|
||||
Assert.assertEquals("username", params.get(0));
|
||||
Assert.assertEquals("Username:", labels.get(0).trim());
|
||||
Assert.assertEquals("password", params.get(1));
|
||||
Assert.assertEquals("Password:", labels.get(1).trim());
|
||||
|
||||
Form form = new Form();
|
||||
form.param("username", "test-user@localhost");
|
||||
form.param("password", "password");
|
||||
response = client.target(callback)
|
||||
.request()
|
||||
.post(Entity.form(form));
|
||||
Assert.assertEquals(302, response.getStatus());
|
||||
String redirect = response.getHeaderString(HttpHeaders.LOCATION);
|
||||
System.out.println("------");
|
||||
System.out.println(redirect);
|
||||
Pattern codePattern = Pattern.compile("code=([^&]+)");
|
||||
m = codePattern.matcher(redirect);
|
||||
Assert.assertTrue(m.find());
|
||||
String code = m.group(1);
|
||||
OAuthClient.AccessTokenResponse oauthResponse = oauth.doAccessTokenRequest(code, "password");
|
||||
Assert.assertNotNull(oauthResponse.getAccessToken());
|
||||
client.close();
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
<#ftl output_format="plainText">
|
||||
${msg("loginTotpIntro")}
|
||||
|
||||
${msg("loginTotpStep1")}
|
||||
|
||||
<#list totp.policy.supportedApplications as app>
|
||||
* ${app}
|
||||
</#list>
|
||||
|
||||
${msg("loginTotpManualStep2")}
|
||||
|
||||
${totp.totpSecretEncoded}
|
||||
|
||||
|
||||
${msg("loginTotpManualStep3")}
|
||||
|
||||
- ${msg("loginTotpType")}: ${msg("loginTotp." + totp.policy.type)}
|
||||
- ${msg("loginTotpAlgorithm")}: ${totp.policy.getAlgorithmKey()}
|
||||
- ${msg("loginTotpDigits")}: ${totp.policy.digits}
|
||||
<#if totp.policy.type = "totp">
|
||||
- ${msg("loginTotpInterval")}: ${totp.policy.period}
|
||||
|
||||
<#elseif totp.policy.type = "hotp">
|
||||
- ${msg("loginTotpCounter")}: ${totp.policy.initialCounter}
|
||||
|
||||
</#if>
|
||||
|
||||
Enter in your one time password so we can verify you have installed it correctly.
|
||||
|
||||
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
<#ftl output_format="plainText">
|
||||
${msg("console-verify-email",email, code)}
|
Loading…
Reference in a new issue