Merge remote-tracking branch 'upstream/master' into client-storage-spi
This commit is contained in:
commit
ddad1cb8af
35 changed files with 1217 additions and 21 deletions
|
@ -56,6 +56,7 @@ public class ClientRepresentation {
|
|||
protected Boolean frontchannelLogout;
|
||||
protected String protocol;
|
||||
protected Map<String, String> attributes;
|
||||
protected Map<String, String> authenticationFlowBindingOverrides;
|
||||
protected Boolean fullScopeAllowed;
|
||||
protected Integer nodeReRegistrationTimeout;
|
||||
protected Map<String, Integer> registeredNodes;
|
||||
|
@ -296,6 +297,14 @@ public class ClientRepresentation {
|
|||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
public Map<String, String> getAuthenticationFlowBindingOverrides() {
|
||||
return authenticationFlowBindingOverrides;
|
||||
}
|
||||
|
||||
public void setAuthenticationFlowBindingOverrides(Map<String, String> authenticationFlowBindingOverrides) {
|
||||
this.authenticationFlowBindingOverrides = authenticationFlowBindingOverrides;
|
||||
}
|
||||
|
||||
public Integer getNodeReRegistrationTimeout() {
|
||||
return nodeReRegistrationTimeout;
|
||||
}
|
||||
|
|
|
@ -345,6 +345,34 @@ public class ClientAdapter implements ClientModel {
|
|||
return copy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthenticationFlowBindingOverride(String name, String value) {
|
||||
getDelegateForUpdate();
|
||||
updated.setAuthenticationFlowBindingOverride(name, value);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAuthenticationFlowBindingOverride(String name) {
|
||||
getDelegateForUpdate();
|
||||
updated.removeAuthenticationFlowBindingOverride(name);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthenticationFlowBindingOverride(String name) {
|
||||
if (isUpdated()) return updated.getAuthenticationFlowBindingOverride(name);
|
||||
return cached.getAuthFlowBindings().get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAuthenticationFlowBindingOverrides() {
|
||||
if (isUpdated()) return updated.getAuthenticationFlowBindingOverrides();
|
||||
Map<String, String> copy = new HashMap<String, String>();
|
||||
copy.putAll(cached.getAuthFlowBindings());
|
||||
return copy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ProtocolMapperModel> getProtocolMappers() {
|
||||
if (isUpdated()) return updated.getProtocolMappers();
|
||||
|
|
|
@ -46,6 +46,7 @@ public class CachedClient extends AbstractRevisioned implements InRealm {
|
|||
protected String registrationToken;
|
||||
protected String protocol;
|
||||
protected Map<String, String> attributes = new HashMap<String, String>();
|
||||
protected Map<String, String> authFlowBindings = new HashMap<String, String>();
|
||||
protected boolean publicClient;
|
||||
protected boolean fullScopeAllowed;
|
||||
protected boolean frontchannelLogout;
|
||||
|
@ -83,6 +84,7 @@ public class CachedClient extends AbstractRevisioned implements InRealm {
|
|||
enabled = model.isEnabled();
|
||||
protocol = model.getProtocol();
|
||||
attributes.putAll(model.getAttributes());
|
||||
authFlowBindings.putAll(model.getAuthenticationFlowBindingOverrides());
|
||||
notBefore = model.getNotBefore();
|
||||
frontchannelLogout = model.isFrontchannelLogout();
|
||||
publicClient = model.isPublicClient();
|
||||
|
@ -256,4 +258,8 @@ public class CachedClient extends AbstractRevisioned implements InRealm {
|
|||
public boolean isUseTemplateMappers() {
|
||||
return useTemplateMappers;
|
||||
}
|
||||
|
||||
public Map<String, String> getAuthFlowBindings() {
|
||||
return authFlowBindings;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -270,6 +270,29 @@ public class ClientAdapter implements ClientModel, JpaModel<ClientEntity> {
|
|||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthenticationFlowBindingOverride(String name, String value) {
|
||||
entity.getAuthFlowBindings().put(name, value);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAuthenticationFlowBindingOverride(String name) {
|
||||
entity.getAuthFlowBindings().remove(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthenticationFlowBindingOverride(String name) {
|
||||
return entity.getAuthFlowBindings().get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAuthenticationFlowBindingOverrides() {
|
||||
Map<String, String> copy = new HashMap<>();
|
||||
copy.putAll(entity.getAuthFlowBindings());
|
||||
return copy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, String value) {
|
||||
entity.getAttributes().put(name, value);
|
||||
|
|
|
@ -119,6 +119,12 @@ public class ClientEntity {
|
|||
@CollectionTable(name="CLIENT_ATTRIBUTES", joinColumns={ @JoinColumn(name="CLIENT_ID") })
|
||||
protected Map<String, String> attributes = new HashMap<String, String>();
|
||||
|
||||
@ElementCollection
|
||||
@MapKeyColumn(name="BINDING_NAME")
|
||||
@Column(name="FLOW_ID", length = 4000)
|
||||
@CollectionTable(name="CLIENT_AUTH_FLOW_BINDINGS", joinColumns={ @JoinColumn(name="CLIENT_ID") })
|
||||
protected Map<String, String> authFlowBindings = new HashMap<String, String>();
|
||||
|
||||
@OneToMany(fetch = FetchType.LAZY, mappedBy = "client", cascade = CascadeType.REMOVE)
|
||||
Collection<ClientIdentityProviderMappingEntity> identityProviders = new ArrayList<ClientIdentityProviderMappingEntity>();
|
||||
|
||||
|
@ -292,6 +298,14 @@ public class ClientEntity {
|
|||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
public Map<String, String> getAuthFlowBindings() {
|
||||
return authFlowBindings;
|
||||
}
|
||||
|
||||
public void setAuthFlowBindings(Map<String, String> authFlowBindings) {
|
||||
this.authFlowBindings = authFlowBindings;
|
||||
}
|
||||
|
||||
public String getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!--
|
||||
~ * 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.
|
||||
-->
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
|
||||
|
||||
<changeSet author="bburke@redhat.com" id="4.0.0-KEYCLOAK-6335">
|
||||
<createTable tableName="CLIENT_AUTH_FLOW_BINDINGS">
|
||||
<column name="CLIENT_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="FLOW_ID" type="VARCHAR(36)"/>
|
||||
<column name="BINDING_NAME" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<addPrimaryKey columnNames="CLIENT_ID, BINDING_NAME" constraintName="C_CLI_FLOW_BIND" tableName="CLIENT_AUTH_FLOW_BINDINGS"/>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
|
@ -53,4 +53,5 @@
|
|||
<include file="META-INF/jpa-changelog-3.4.0.xml"/>
|
||||
<include file="META-INF/jpa-changelog-3.4.1.xml"/>
|
||||
<include file="META-INF/jpa-changelog-3.4.2.xml"/>
|
||||
<include file="META-INF/jpa-changelog-4.0.0.xml"/>
|
||||
</databaseChangeLog>
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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.models.utils;
|
||||
|
||||
import org.keycloak.models.AuthenticationFlowBindings;
|
||||
import org.keycloak.models.AuthenticationFlowModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class AuthenticationFlowResolver {
|
||||
|
||||
public static AuthenticationFlowModel resolveBrowserFlow(AuthenticationSessionModel authSession) {
|
||||
AuthenticationFlowModel flow = null;
|
||||
ClientModel client = authSession.getClient();
|
||||
String clientFlow = client.getAuthenticationFlowBindingOverride(AuthenticationFlowBindings.BROWSER_BINDING);
|
||||
if (clientFlow != null) {
|
||||
flow = authSession.getRealm().getAuthenticationFlowById(clientFlow);
|
||||
if (flow == null) {
|
||||
throw new ModelException("Client " + client.getClientId() + " has browser flow override, but this flow does not exist");
|
||||
}
|
||||
return flow;
|
||||
}
|
||||
return authSession.getRealm().getBrowserFlow();
|
||||
}
|
||||
public static AuthenticationFlowModel resolveDirectGrantFlow(AuthenticationSessionModel authSession) {
|
||||
AuthenticationFlowModel flow = null;
|
||||
ClientModel client = authSession.getClient();
|
||||
String clientFlow = client.getAuthenticationFlowBindingOverride(AuthenticationFlowBindings.DIRECT_GRANT_BINDING);
|
||||
if (clientFlow != null) {
|
||||
flow = authSession.getRealm().getAuthenticationFlowById(clientFlow);
|
||||
if (flow == null) {
|
||||
throw new ModelException("Client " + client.getClientId() + " has direct grant flow override, but this flow does not exist");
|
||||
}
|
||||
return flow;
|
||||
}
|
||||
return authSession.getRealm().getDirectGrantFlow();
|
||||
}
|
||||
}
|
|
@ -494,6 +494,7 @@ public class ModelToRepresentation {
|
|||
rep.setFrontchannelLogout(clientModel.isFrontchannelLogout());
|
||||
rep.setProtocol(clientModel.getProtocol());
|
||||
rep.setAttributes(clientModel.getAttributes());
|
||||
rep.setAuthenticationFlowBindingOverrides(clientModel.getAuthenticationFlowBindingOverrides());
|
||||
rep.setFullScopeAllowed(clientModel.isFullScopeAllowed());
|
||||
rep.setBearerOnly(clientModel.isBearerOnly());
|
||||
rep.setConsentRequired(clientModel.isConsentRequired());
|
||||
|
|
|
@ -1084,6 +1084,17 @@ public class RepresentationToModel {
|
|||
}
|
||||
|
||||
|
||||
if (resourceRep.getAuthenticationFlowBindingOverrides() != null) {
|
||||
for (Map.Entry<String, String> entry : resourceRep.getAuthenticationFlowBindingOverrides().entrySet()) {
|
||||
if (entry.getValue() == null || entry.getValue().trim().equals("")) {
|
||||
continue;
|
||||
} else {
|
||||
client.setAuthenticationFlowBindingOverride(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (resourceRep.getRedirectUris() != null) {
|
||||
for (String redirectUri : resourceRep.getRedirectUris()) {
|
||||
client.addRedirectUri(redirectUri);
|
||||
|
@ -1201,6 +1212,22 @@ public class RepresentationToModel {
|
|||
resource.setAttribute(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
if (rep.getAttributes() != null) {
|
||||
for (Map.Entry<String, String> entry : rep.getAttributes().entrySet()) {
|
||||
resource.setAttribute(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
if (rep.getAuthenticationFlowBindingOverrides() != null) {
|
||||
for (Map.Entry<String, String> entry : rep.getAuthenticationFlowBindingOverrides().entrySet()) {
|
||||
if (entry.getValue() == null || entry.getValue().trim().equals("")) {
|
||||
resource.removeAuthenticationFlowBindingOverride(entry.getKey());
|
||||
} else {
|
||||
resource.setAuthenticationFlowBindingOverride(entry.getKey(), entry.getValue());
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (rep.getNotBefore() != null) {
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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.models;
|
||||
|
||||
/**
|
||||
* Defines constants for authentication flow bindings. Strings used for lookup
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface AuthenticationFlowBindings {
|
||||
String BROWSER_BINDING = "browser";
|
||||
String DIRECT_GRANT_BINDING = "direct_grant";
|
||||
}
|
|
@ -118,6 +118,18 @@ public interface ClientModel extends RoleContainerModel, ProtocolMapperContaine
|
|||
String getAttribute(String name);
|
||||
Map<String, String> getAttributes();
|
||||
|
||||
/**
|
||||
* Get authentication flow binding override for this client. Allows client to override an authentication flow binding.
|
||||
*
|
||||
* @param binding examples are "browser", "direct_grant"
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getAuthenticationFlowBindingOverride(String binding);
|
||||
public Map<String, String> getAuthenticationFlowBindingOverrides();
|
||||
public void removeAuthenticationFlowBindingOverride(String binding);
|
||||
public void setAuthenticationFlowBindingOverride(String binding, String flowId);
|
||||
|
||||
boolean isFrontchannelLogout();
|
||||
void setFrontchannelLogout(boolean flag);
|
||||
|
||||
|
|
|
@ -68,6 +68,10 @@ public class OTPPolicy implements Serializable {
|
|||
|
||||
public static OTPPolicy DEFAULT_POLICY = new OTPPolicy(UserCredentialModel.TOTP, HmacOTP.HMAC_SHA1, 0, 6, 1, 30);
|
||||
|
||||
public String getAlgorithmKey() {
|
||||
return algToKeyUriAlg.containsKey(algorithm) ? algToKeyUriAlg.get(algorithm) : algorithm;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ import org.keycloak.models.KeycloakSession;
|
|||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.utils.AuthenticationFlowResolver;
|
||||
import org.keycloak.models.utils.FormMessage;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.protocol.LoginProtocol;
|
||||
|
@ -646,7 +647,7 @@ public class AuthenticationProcessor {
|
|||
AuthenticationProcessor processor = new AuthenticationProcessor();
|
||||
processor.setAuthenticationSession(clone)
|
||||
.setFlowPath(LoginActionsService.AUTHENTICATE_PATH)
|
||||
.setFlowId(realm.getBrowserFlow().getId())
|
||||
.setFlowId(AuthenticationFlowResolver.resolveBrowserFlow(clone).getId())
|
||||
.setForwardedErrorMessage(reset.getErrorMessage())
|
||||
.setForwardedSuccessMessage(reset.getSuccessMessage())
|
||||
.setConnection(connection)
|
||||
|
|
|
@ -39,6 +39,22 @@ import java.security.KeyStore;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* The default {@link HttpClientFactory} for {@link HttpClientProvider HttpClientProvider's} used by Keycloak for outbound HTTP calls.
|
||||
* <p>
|
||||
* The constructed clients can be configured via Keycloaks SPI configuration, e.g. {@code standalone.xml, standalone-ha.xml, domain.xml}.
|
||||
* </p>
|
||||
* <p>
|
||||
* Examples for jboss-cli
|
||||
* </p>
|
||||
* <pre>
|
||||
* {@code
|
||||
*
|
||||
* /subsystem=keycloak-server/spi=connectionsHttpClient/provider=default:add(enabled=true)
|
||||
* /subsystem=keycloak-server/spi=connectionsHttpClient/provider=default:write-attribute(name=properties.connection-pool-size,value=128)
|
||||
* /subsystem=keycloak-server/spi=connectionsHttpClient/provider=default:write-attribute(name=properties.proxy-mappings,value=[".*\\.(google|googleapis)\\.com;http://www-proxy.acme.corp.com:8080",".*\\.acme\\.corp\\.com;NO_PROXY",".*;http://fallback:8080"])
|
||||
* }
|
||||
* </pre>
|
||||
* </p>
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class DefaultHttpClientFactory implements HttpClientFactory {
|
||||
|
@ -127,6 +143,7 @@ public class DefaultHttpClientFactory implements HttpClientFactory {
|
|||
String clientKeystore = config.get("client-keystore");
|
||||
String clientKeystorePassword = config.get("client-keystore-password");
|
||||
String clientPrivateKeyPassword = config.get("client-key-password");
|
||||
String[] proxyMappings = config.getArray("proxy-mappings");
|
||||
|
||||
TruststoreProvider truststoreProvider = session.getProvider(TruststoreProvider.class);
|
||||
boolean disableTrustManager = truststoreProvider == null || truststoreProvider.getTruststore() == null;
|
||||
|
@ -137,13 +154,15 @@ public class DefaultHttpClientFactory implements HttpClientFactory {
|
|||
: HttpClientBuilder.HostnameVerificationPolicy.valueOf(truststoreProvider.getPolicy().name());
|
||||
|
||||
HttpClientBuilder builder = new HttpClientBuilder();
|
||||
|
||||
builder.socketTimeout(socketTimeout, TimeUnit.MILLISECONDS)
|
||||
.establishConnectionTimeout(establishConnectionTimeout, TimeUnit.MILLISECONDS)
|
||||
.maxPooledPerRoute(maxPooledPerRoute)
|
||||
.connectionPoolSize(connectionPoolSize)
|
||||
.connectionTTL(connectionTTL, TimeUnit.MILLISECONDS)
|
||||
.maxConnectionIdleTime(maxConnectionIdleTime, TimeUnit.MILLISECONDS)
|
||||
.disableCookies(disableCookies);
|
||||
.disableCookies(disableCookies)
|
||||
.proxyMappings(ProxyMappings.valueOf(proxyMappings));
|
||||
|
||||
if (disableTrustManager) {
|
||||
// TODO: is it ok to do away with disabling trust manager?
|
||||
|
|
|
@ -52,7 +52,7 @@ import java.util.concurrent.TimeUnit;
|
|||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class HttpClientBuilder {
|
||||
public static enum HostnameVerificationPolicy {
|
||||
public enum HostnameVerificationPolicy {
|
||||
/**
|
||||
* Hostname verification is not done on the server's certificate
|
||||
*/
|
||||
|
@ -104,7 +104,7 @@ public class HttpClientBuilder {
|
|||
protected long establishConnectionTimeout = -1;
|
||||
protected TimeUnit establishConnectionTimeoutUnits = TimeUnit.MILLISECONDS;
|
||||
protected boolean disableCookies = false;
|
||||
|
||||
protected ProxyMappings proxyMappings;
|
||||
|
||||
/**
|
||||
* Socket inactivity timeout
|
||||
|
@ -208,6 +208,11 @@ public class HttpClientBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
public HttpClientBuilder proxyMappings(ProxyMappings proxyMappings) {
|
||||
this.proxyMappings = proxyMappings;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
static class VerifierWrapper implements X509HostnameVerifier {
|
||||
protected HostnameVerifier verifier;
|
||||
|
@ -272,6 +277,7 @@ public class HttpClientBuilder {
|
|||
tlsContext.init(null, null, null);
|
||||
sslsf = new SSLConnectionSocketFactory(tlsContext, verifier);
|
||||
}
|
||||
|
||||
RequestConfig requestConfig = RequestConfig.custom()
|
||||
.setConnectTimeout((int) establishConnectionTimeout)
|
||||
.setSocketTimeout((int) socketTimeout).build();
|
||||
|
@ -283,6 +289,11 @@ public class HttpClientBuilder {
|
|||
.setMaxConnPerRoute(maxPooledPerRoute)
|
||||
.setConnectionTimeToLive(connectionTTL, connectionTTLUnit);
|
||||
|
||||
|
||||
if (proxyMappings != null && !proxyMappings.isEmpty()) {
|
||||
builder.setRoutePlanner(new ProxyMappingsAwareRoutePlanner(proxyMappings));
|
||||
}
|
||||
|
||||
if (maxConnectionIdleTime > 0) {
|
||||
// Will start background cleaner thread
|
||||
builder.evictIdleConnections(maxConnectionIdleTime, maxConnectionIdleTimeUnit);
|
||||
|
|
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
* 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.connections.httpclient;
|
||||
|
||||
import org.apache.http.HttpHost;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* {@link ProxyMappings} describes an ordered mapping for hostname regex patterns to a {@link HttpHost} proxy.
|
||||
* <p>
|
||||
* Mappings can be created via {@link #valueOf(String...)} or {@link #valueOf(List)}.
|
||||
* For a description of the mapping format see {@link ProxyMapping#valueOf(String)}
|
||||
*
|
||||
* @author <a href="mailto:thomas.darimont@gmail.com">Thomas Darimont</a>
|
||||
*/
|
||||
public class ProxyMappings {
|
||||
|
||||
private static final ProxyMappings EMPTY_MAPPING = valueOf(Collections.emptyList());
|
||||
|
||||
private final List<ProxyMapping> entries;
|
||||
|
||||
/**
|
||||
* Creates a {@link ProxyMappings} from the provided {@link ProxyMapping Entries}.
|
||||
*
|
||||
* @param entries
|
||||
*/
|
||||
public ProxyMappings(List<ProxyMapping> entries) {
|
||||
this.entries = Collections.unmodifiableList(entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ProxyMappings} from the provided {@code List} of proxy mapping strings.
|
||||
* <p>
|
||||
*
|
||||
* @param proxyMappings
|
||||
*/
|
||||
public static ProxyMappings valueOf(List<String> proxyMappings) {
|
||||
|
||||
if (proxyMappings == null || proxyMappings.isEmpty()) {
|
||||
return EMPTY_MAPPING;
|
||||
}
|
||||
|
||||
List<ProxyMapping> entries = proxyMappings.stream() //
|
||||
.map(ProxyMapping::valueOf) //
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return new ProxyMappings(entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ProxyMappings} from the provided {@code String[]} of proxy mapping strings.
|
||||
*
|
||||
* @param proxyMappings
|
||||
* @return
|
||||
* @see #valueOf(List)
|
||||
* @see ProxyMapping#valueOf(String...)
|
||||
*/
|
||||
public static ProxyMappings valueOf(String... proxyMappings) {
|
||||
|
||||
if (proxyMappings == null || proxyMappings.length == 0) {
|
||||
return EMPTY_MAPPING;
|
||||
}
|
||||
|
||||
return valueOf(Arrays.asList(proxyMappings));
|
||||
}
|
||||
|
||||
|
||||
public boolean isEmpty() {
|
||||
return this.entries.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param hostname
|
||||
* @return the {@link HttpHost} proxy associated with the first matching hostname {@link Pattern}
|
||||
* or {@literal null} if none matches.
|
||||
*/
|
||||
public HttpHost getProxyFor(String hostname) {
|
||||
|
||||
Objects.requireNonNull(hostname, "hostname");
|
||||
|
||||
return entries.stream() //
|
||||
.filter(e -> e.matches(hostname)) //
|
||||
.findFirst() //
|
||||
.map(ProxyMapping::getProxy) //
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ProxyMapping} describes a Proxy Mapping with a Hostname {@link Pattern}
|
||||
* that is mapped to a proxy {@link HttpHost}.
|
||||
*/
|
||||
public static class ProxyMapping {
|
||||
|
||||
public static final String NO_PROXY = "NO_PROXY";
|
||||
private static final String DELIMITER = ";";
|
||||
|
||||
private final Pattern hostnamePattern;
|
||||
|
||||
private final HttpHost proxy;
|
||||
|
||||
public ProxyMapping(Pattern hostnamePattern, HttpHost proxy) {
|
||||
this.hostnamePattern = hostnamePattern;
|
||||
this.proxy = proxy;
|
||||
}
|
||||
|
||||
public Pattern getHostnamePattern() {
|
||||
return hostnamePattern;
|
||||
}
|
||||
|
||||
public HttpHost getProxy() {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
public boolean matches(String hostname) {
|
||||
return getHostnamePattern().matcher(hostname).matches();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a mapping string into an {@link ProxyMapping}.
|
||||
* <p>
|
||||
* A proxy mapping string must have the following format: {@code hostnameRegex;www-proxy-uri}
|
||||
* with semicolon as a delimiter.</p>
|
||||
* <p>
|
||||
* If no proxy should be used for a host pattern then use {@code NO_PROXY} as www-proxy-uri.
|
||||
* </p>
|
||||
* <p>Examples:
|
||||
* <pre>
|
||||
* {@code
|
||||
*
|
||||
* .*\.(google\.com|googleapis\.com);http://www-proxy.acme.corp.com:8080
|
||||
* .*\.acme\.corp\.com;NO_PROXY
|
||||
* .*;http://fallback:8080
|
||||
* }
|
||||
* </pre>
|
||||
* </p>
|
||||
*
|
||||
* @param mapping
|
||||
* @return
|
||||
*/
|
||||
public static ProxyMapping valueOf(String mapping) {
|
||||
|
||||
String[] mappingTokens = mapping.split(DELIMITER);
|
||||
|
||||
String hostPatternRegex = mappingTokens[0];
|
||||
String proxyUriString = mappingTokens[1];
|
||||
|
||||
Pattern hostPattern = Pattern.compile(hostPatternRegex);
|
||||
HttpHost proxyHost = toProxyHost(proxyUriString);
|
||||
|
||||
return new ProxyMapping(hostPattern, proxyHost);
|
||||
}
|
||||
|
||||
private static HttpHost toProxyHost(String proxyUriString) {
|
||||
|
||||
if (NO_PROXY.equals(proxyUriString)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
URI uri = URI.create(proxyUriString);
|
||||
return new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ProxyMapping{" +
|
||||
"hostnamePattern=" + hostnamePattern +
|
||||
", proxy=" + proxy +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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.connections.httpclient;
|
||||
|
||||
import org.apache.http.HttpException;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.impl.conn.DefaultRoutePlanner;
|
||||
import org.apache.http.impl.conn.DefaultSchemePortResolver;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* A {@link DefaultRoutePlanner} that determines the proxy to use for a given target hostname by consulting
|
||||
* the given {@link ProxyMappings}.
|
||||
*
|
||||
* @author <a href="mailto:thomas.darimont@gmail.com">Thomas Darimont</a>
|
||||
* @see ProxyMappings
|
||||
*/
|
||||
public class ProxyMappingsAwareRoutePlanner extends DefaultRoutePlanner {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(ProxyMappingsAwareRoutePlanner.class);
|
||||
|
||||
private final ProxyMappings proxyMappings;
|
||||
|
||||
public ProxyMappingsAwareRoutePlanner(ProxyMappings proxyMappings) {
|
||||
super(DefaultSchemePortResolver.INSTANCE);
|
||||
this.proxyMappings = proxyMappings;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpHost determineProxy(HttpHost target, HttpRequest request, HttpContext context) throws HttpException {
|
||||
|
||||
HttpHost proxy = proxyMappings.getProxyFor(target.getHostName());
|
||||
LOG.debugf("Returning proxy=%s for targetHost=%s", proxy, target.getHostName());
|
||||
|
||||
return proxy;
|
||||
}
|
||||
}
|
|
@ -30,6 +30,7 @@ import org.keycloak.models.ClientModel;
|
|||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.utils.AuthenticationFlowResolver;
|
||||
import org.keycloak.protocol.LoginProtocol.Error;
|
||||
import org.keycloak.services.ErrorPageException;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
|
@ -107,7 +108,7 @@ public abstract class AuthorizationEndpointBase {
|
|||
* @return response to be returned to the browser
|
||||
*/
|
||||
protected Response handleBrowserAuthenticationRequest(AuthenticationSessionModel authSession, LoginProtocol protocol, boolean isPassive, boolean redirectToAuthentication) {
|
||||
AuthenticationFlowModel flow = getAuthenticationFlow();
|
||||
AuthenticationFlowModel flow = getAuthenticationFlow(authSession);
|
||||
String flowId = flow.getId();
|
||||
AuthenticationProcessor processor = createProcessor(authSession, flowId, LoginActionsService.AUTHENTICATE_PATH);
|
||||
event.detail(Details.CODE_ID, authSession.getParentSession().getId());
|
||||
|
@ -149,8 +150,8 @@ public abstract class AuthorizationEndpointBase {
|
|||
}
|
||||
}
|
||||
|
||||
protected AuthenticationFlowModel getAuthenticationFlow() {
|
||||
return realm.getBrowserFlow();
|
||||
protected AuthenticationFlowModel getAuthenticationFlow(AuthenticationSessionModel authSession) {
|
||||
return AuthenticationFlowResolver.resolveBrowserFlow(authSession);
|
||||
}
|
||||
|
||||
protected void checkSsl() {
|
||||
|
|
|
@ -88,7 +88,7 @@ public class DockerEndpoint extends AuthorizationEndpointBase {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected AuthenticationFlowModel getAuthenticationFlow() {
|
||||
protected AuthenticationFlowModel getAuthenticationFlow(AuthenticationSessionModel authSession) {
|
||||
return realm.getDockerAuthenticationFlow();
|
||||
}
|
||||
|
||||
|
|
|
@ -56,6 +56,7 @@ import org.keycloak.models.KeycloakSessionFactory;
|
|||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.utils.AuthenticationFlowResolver;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.TokenManager;
|
||||
|
@ -491,7 +492,7 @@ public class TokenEndpoint {
|
|||
authSession.setClientNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
|
||||
authSession.setClientNote(OIDCLoginProtocol.SCOPE_PARAM, scope);
|
||||
|
||||
AuthenticationFlowModel flow = realm.getDirectGrantFlow();
|
||||
AuthenticationFlowModel flow = AuthenticationFlowResolver.resolveDirectGrantFlow(authSession);
|
||||
String flowId = flow.getId();
|
||||
AuthenticationProcessor processor = new AuthenticationProcessor();
|
||||
processor.setAuthenticationSession(authSession)
|
||||
|
|
|
@ -147,7 +147,7 @@ public class SamlEcpProfileService extends SamlService {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected AuthenticationFlowModel getAuthenticationFlow() {
|
||||
protected AuthenticationFlowModel getAuthenticationFlow(AuthenticationSessionModel authSession) {
|
||||
for (AuthenticationFlowModel flowModel : realm.getAuthenticationFlows()) {
|
||||
if (flowModel.getAlias().equals(DefaultAuthenticationFlows.SAML_ECP_FLOW)) {
|
||||
return flowModel;
|
||||
|
|
|
@ -55,6 +55,7 @@ import org.keycloak.models.RealmModel;
|
|||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.utils.AuthenticationFlowResolver;
|
||||
import org.keycloak.models.utils.FormMessage;
|
||||
import org.keycloak.protocol.LoginProtocol;
|
||||
import org.keycloak.protocol.LoginProtocolFactory;
|
||||
|
@ -1121,7 +1122,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
|||
|
||||
protected Response browserAuthentication(AuthenticationSessionModel authSession, String errorMessage) {
|
||||
this.event.event(EventType.LOGIN);
|
||||
AuthenticationFlowModel flow = realmModel.getBrowserFlow();
|
||||
AuthenticationFlowModel flow = AuthenticationFlowResolver.resolveBrowserFlow(authSession);
|
||||
String flowId = flow.getId();
|
||||
AuthenticationProcessor processor = new AuthenticationProcessor();
|
||||
processor.setAuthenticationSession(authSession)
|
||||
|
|
|
@ -53,6 +53,7 @@ import org.keycloak.models.RealmModel;
|
|||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserConsentModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.AuthenticationFlowResolver;
|
||||
import org.keycloak.models.utils.FormMessage;
|
||||
import org.keycloak.models.utils.SystemClientUtil;
|
||||
import org.keycloak.protocol.AuthorizationEndpointBase;
|
||||
|
@ -252,7 +253,7 @@ public class LoginActionsService {
|
|||
}
|
||||
|
||||
protected Response processAuthentication(boolean action, String execution, AuthenticationSessionModel authSession, String errorMessage) {
|
||||
return processFlow(action, execution, authSession, AUTHENTICATE_PATH, realm.getBrowserFlow(), errorMessage, new AuthenticationProcessor());
|
||||
return processFlow(action, execution, authSession, AUTHENTICATE_PATH, AuthenticationFlowResolver.resolveBrowserFlow(authSession), errorMessage, new AuthenticationProcessor());
|
||||
}
|
||||
|
||||
protected Response processFlow(boolean action, String execution, AuthenticationSessionModel authSession, String flowPath, AuthenticationFlowModel flow, String errorMessage, AuthenticationProcessor processor) {
|
||||
|
|
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
* 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.connections.httpclient;
|
||||
|
||||
import org.apache.http.HttpHost;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.hamcrest.CoreMatchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ProxyMappings}.
|
||||
*
|
||||
* @author <a href="mailto:thomas.darimont@gmail.com">Thomas Darimont</a>
|
||||
*/
|
||||
public class ProxyMappingsTest {
|
||||
|
||||
private static final List<String> DEFAULT_MAPPINGS = Arrays.asList( //
|
||||
".*\\.(google|googleapis)\\.com;http://proxy1:8080", //
|
||||
".*\\.facebook\\.com;http://proxy2:8080" //
|
||||
);
|
||||
|
||||
private static final List<String> MAPPINGS_WITH_FALLBACK = new ArrayList<>();
|
||||
|
||||
private static final List<String> MAPPINGS_WITH_FALLBACK_AND_PROXY_EXCEPTION = new ArrayList<>();
|
||||
|
||||
static {
|
||||
MAPPINGS_WITH_FALLBACK.addAll(DEFAULT_MAPPINGS);
|
||||
MAPPINGS_WITH_FALLBACK.add(".*;http://fallback:8080");
|
||||
}
|
||||
|
||||
static {
|
||||
MAPPINGS_WITH_FALLBACK_AND_PROXY_EXCEPTION.addAll(DEFAULT_MAPPINGS);
|
||||
MAPPINGS_WITH_FALLBACK_AND_PROXY_EXCEPTION.add(".*\\.acme\\.corp\\.com;NO_PROXY");
|
||||
MAPPINGS_WITH_FALLBACK_AND_PROXY_EXCEPTION.add(".*;http://fallback:8080");
|
||||
}
|
||||
|
||||
@Rule
|
||||
public ExpectedException expectedException = ExpectedException.none();
|
||||
|
||||
ProxyMappings proxyMappings;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
proxyMappings = ProxyMappings.valueOf(DEFAULT_MAPPINGS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void proxyMappingFromEmptyListShouldBeEmpty() {
|
||||
assertThat(new ProxyMappings(new ArrayList<>()).isEmpty(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnProxy1ForConfiguredProxyMapping() {
|
||||
|
||||
HttpHost proxy = proxyMappings.getProxyFor("account.google.com");
|
||||
assertThat(proxy, is(notNullValue()));
|
||||
assertThat(proxy.getHostName(), is("proxy1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnProxy1ForConfiguredProxyMappingAlternative() {
|
||||
|
||||
HttpHost proxy = proxyMappings.getProxyFor("www.googleapis.com");
|
||||
assertThat(proxy, is(notNullValue()));
|
||||
assertThat(proxy.getHostName(), is("proxy1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnProxy1ForConfiguredProxyMappingWithSubDomain() {
|
||||
|
||||
HttpHost proxy = proxyMappings.getProxyFor("awesome.account.google.com");
|
||||
assertThat(proxy, is(notNullValue()));
|
||||
assertThat(proxy.getHostName(), is("proxy1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnProxy2ForConfiguredProxyMapping() {
|
||||
|
||||
HttpHost proxy = proxyMappings.getProxyFor("login.facebook.com");
|
||||
assertThat(proxy, is(notNullValue()));
|
||||
assertThat(proxy.getHostName(), is("proxy2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnNoProxyForUnknownHost() {
|
||||
|
||||
HttpHost proxy = proxyMappings.getProxyFor("login.microsoft.com");
|
||||
assertThat(proxy, is(nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRejectNull() {
|
||||
|
||||
expectedException.expect(NullPointerException.class);
|
||||
expectedException.expectMessage("hostname");
|
||||
|
||||
proxyMappings.getProxyFor(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnFallbackForNotExplicitlyMappedHostname() {
|
||||
|
||||
ProxyMappings proxyMappingsWithFallback = ProxyMappings.valueOf(MAPPINGS_WITH_FALLBACK);
|
||||
|
||||
HttpHost proxy = proxyMappingsWithFallback.getProxyFor("login.salesforce.com");
|
||||
assertThat(proxy.getHostName(), is("fallback"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnCorrectProxyOrFallback() {
|
||||
|
||||
ProxyMappings proxyMappingsWithFallback = ProxyMappings.valueOf(MAPPINGS_WITH_FALLBACK);
|
||||
|
||||
HttpHost forGoogle = proxyMappingsWithFallback.getProxyFor("login.google.com");
|
||||
assertThat(forGoogle.getHostName(), is("proxy1"));
|
||||
|
||||
HttpHost forFacebook = proxyMappingsWithFallback.getProxyFor("login.facebook.com");
|
||||
assertThat(forFacebook.getHostName(), is("proxy2"));
|
||||
|
||||
HttpHost forMicrosoft = proxyMappingsWithFallback.getProxyFor("login.microsoft.com");
|
||||
assertThat(forMicrosoft.getHostName(), is("fallback"));
|
||||
|
||||
HttpHost forSalesForce = proxyMappingsWithFallback.getProxyFor("login.salesforce.com");
|
||||
assertThat(forSalesForce.getHostName(), is("fallback"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnFallbackForNotExplicitlyMappedHostnameAndHonorProxyExceptions() {
|
||||
|
||||
ProxyMappings proxyMappingsWithFallbackAndProxyException = ProxyMappings.valueOf(MAPPINGS_WITH_FALLBACK_AND_PROXY_EXCEPTION);
|
||||
|
||||
HttpHost forGoogle = proxyMappingsWithFallbackAndProxyException.getProxyFor("login.google.com");
|
||||
assertThat(forGoogle.getHostName(), is("proxy1"));
|
||||
|
||||
HttpHost forFacebook = proxyMappingsWithFallbackAndProxyException.getProxyFor("login.facebook.com");
|
||||
assertThat(forFacebook.getHostName(), is("proxy2"));
|
||||
|
||||
HttpHost forAcmeCorp = proxyMappingsWithFallbackAndProxyException.getProxyFor("myapp.acme.corp.com");
|
||||
assertThat(forAcmeCorp, is(nullValue()));
|
||||
|
||||
HttpHost forMicrosoft = proxyMappingsWithFallbackAndProxyException.getProxyFor("login.microsoft.com");
|
||||
assertThat(forMicrosoft.getHostName(), is("fallback"));
|
||||
|
||||
HttpHost forSalesForce = proxyMappingsWithFallbackAndProxyException.getProxyFor("login.salesforce.com");
|
||||
assertThat(forSalesForce.getHostName(), is("fallback"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* 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.testsuite.forms;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.authentication.AuthenticationFlowContext;
|
||||
import org.keycloak.authentication.AuthenticationFlowError;
|
||||
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.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class UsernameOnlyAuthenticator implements Authenticator, AuthenticatorFactory {
|
||||
public static final String PROVIDER_ID = "testsuite-username";
|
||||
|
||||
@Override
|
||||
public void authenticate(AuthenticationFlowContext context) {
|
||||
String username = context.getHttpRequest().getDecodedFormParameters().getFirst("username");
|
||||
UserModel user = KeycloakModelUtils.findUserByNameOrEmail(context.getSession(), context.getRealm(), username);
|
||||
if (user == null) {
|
||||
context.failure(AuthenticationFlowError.UNKNOWN_USER);
|
||||
return;
|
||||
}
|
||||
context.setUser(user);
|
||||
context.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresUser() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@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 action(AuthenticationFlowContext context) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayType() {
|
||||
return "Testsuite Username Only";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReferenceCategory() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@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 boolean isUserSetupAllowed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "Testsuite Username authenticator. Username parameter sets username";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authenticator create(KeycloakSession session) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
}
|
|
@ -19,4 +19,5 @@ org.keycloak.testsuite.forms.PassThroughAuthenticator
|
|||
org.keycloak.testsuite.forms.PassThroughRegistration
|
||||
org.keycloak.testsuite.forms.ClickThroughAuthenticator
|
||||
org.keycloak.testsuite.authentication.ExpectedParamAuthenticatorFactory
|
||||
org.keycloak.testsuite.authentication.PushButtonAuthenticatorFactory
|
||||
org.keycloak.testsuite.authentication.PushButtonAuthenticatorFactory
|
||||
org.keycloak.testsuite.forms.UsernameOnlyAuthenticator
|
|
@ -837,7 +837,7 @@ public class AccountFormServiceTest extends AbstractTestRealmKeycloakTest {
|
|||
assertTrue(driver.findElement(By.id("kc-totp-secret-key")).getText().matches("[\\w]{4}( [\\w]{4}){7}"));
|
||||
|
||||
assertEquals("Type: Time-based", driver.findElement(By.id("kc-totp-type")).getText());
|
||||
assertEquals("Algorithm: HmacSHA1", driver.findElement(By.id("kc-totp-algorithm")).getText());
|
||||
assertEquals("Algorithm: SHA1", driver.findElement(By.id("kc-totp-algorithm")).getText());
|
||||
assertEquals("Digits: 6", driver.findElement(By.id("kc-totp-digits")).getText());
|
||||
assertEquals("Interval: 30", driver.findElement(By.id("kc-totp-period")).getText());
|
||||
|
||||
|
|
|
@ -177,7 +177,7 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
|
|||
assertTrue(driver.findElement(By.id("kc-totp-secret-key")).getText().matches("[\\w]{4}( [\\w]{4}){7}"));
|
||||
|
||||
assertEquals("Type: Time-based", driver.findElement(By.id("kc-totp-type")).getText());
|
||||
assertEquals("Algorithm: HmacSHA1", driver.findElement(By.id("kc-totp-algorithm")).getText());
|
||||
assertEquals("Algorithm: SHA1", driver.findElement(By.id("kc-totp-algorithm")).getText());
|
||||
assertEquals("Digits: 6", driver.findElement(By.id("kc-totp-digits")).getText());
|
||||
assertEquals("Interval: 30", driver.findElement(By.id("kc-totp-period")).getText());
|
||||
|
||||
|
@ -217,9 +217,9 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
|
|||
totpPage.clickManual();
|
||||
|
||||
assertEquals("Type: Counter-based", driver.findElement(By.id("kc-totp-type")).getText());
|
||||
assertEquals("Algorithm: HmacSHA256", driver.findElement(By.id("kc-totp-algorithm")).getText());
|
||||
assertEquals("Algorithm: SHA256", driver.findElement(By.id("kc-totp-algorithm")).getText());
|
||||
assertEquals("Digits: 8", driver.findElement(By.id("kc-totp-digits")).getText());
|
||||
assertEquals("Interval: 30", driver.findElement(By.id("kc-totp-period")).getText());
|
||||
assertEquals("Counter: 0", driver.findElement(By.id("kc-totp-counter")).getText());
|
||||
} finally {
|
||||
rep.setOtpPolicyDigits(6);
|
||||
rep.setOtpPolicyType("totp");
|
||||
|
|
|
@ -180,6 +180,8 @@ public class ProvidersTest extends AbstractAuthenticationTest {
|
|||
"Testsuite Dummy authenticator. Just passes through and is hardcoded to a specific user");
|
||||
addProviderInfo(result, "testsuite-dummy-registration", "Testsuite Dummy Pass Thru",
|
||||
"Testsuite Dummy authenticator. Just passes through and is hardcoded to a specific user");
|
||||
addProviderInfo(result, "testsuite-username", "Testsuite Username Only",
|
||||
"Testsuite Username authenticator. Username parameter sets username");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,353 @@
|
|||
/*
|
||||
* 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.container.test.api.Deployment;
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.jboss.shrinkwrap.api.spec.WebArchive;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.admin.client.resource.ClientsResource;
|
||||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.authentication.authenticators.browser.UsernamePasswordFormFactory;
|
||||
import org.keycloak.events.Details;
|
||||
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.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.authentication.PushButtonAuthenticatorFactory;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
import org.keycloak.testsuite.pages.ErrorPage;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
|
||||
import org.keycloak.util.BasicAuthHelper;
|
||||
import org.openqa.selenium.By;
|
||||
|
||||
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.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Test that clients can override auth flows
|
||||
*
|
||||
* @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
|
||||
*/
|
||||
public class FlowOverrideTest 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) {
|
||||
}
|
||||
|
||||
@Deployment
|
||||
public static WebArchive deploy() {
|
||||
return RunOnServerDeployment.create(UserResource.class)
|
||||
.addPackages(true, "org.keycloak.testsuite");
|
||||
}
|
||||
|
||||
|
||||
@Before
|
||||
public void setupFlows() {
|
||||
testingClient.server().run(session -> {
|
||||
RealmModel realm = session.realms().getRealmByName("test");
|
||||
|
||||
ClientModel client = session.realms().getClientByClientId("test-app-flow", realm);
|
||||
if (client != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
client = session.realms().getClientByClientId("test-app", realm);
|
||||
client.setDirectAccessGrantsEnabled(true);
|
||||
|
||||
|
||||
|
||||
// Parent flow
|
||||
AuthenticationFlowModel browser = new AuthenticationFlowModel();
|
||||
browser.setAlias("parent-flow");
|
||||
browser.setDescription("browser based authentication");
|
||||
browser.setProviderId("basic-flow");
|
||||
browser.setTopLevel(true);
|
||||
browser.setBuiltIn(true);
|
||||
browser = realm.addAuthenticationFlow(browser);
|
||||
|
||||
// Subflow2
|
||||
AuthenticationFlowModel subflow2 = new AuthenticationFlowModel();
|
||||
subflow2.setTopLevel(false);
|
||||
subflow2.setBuiltIn(true);
|
||||
subflow2.setAlias("subflow-2");
|
||||
subflow2.setDescription("username+password AND pushButton");
|
||||
subflow2.setProviderId("basic-flow");
|
||||
subflow2 = realm.addAuthenticationFlow(subflow2);
|
||||
|
||||
AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
|
||||
execution.setParentFlow(browser.getId());
|
||||
execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
|
||||
execution.setFlowId(subflow2.getId());
|
||||
execution.setPriority(20);
|
||||
execution.setAuthenticatorFlow(true);
|
||||
realm.addAuthenticatorExecution(execution);
|
||||
|
||||
// Subflow2 - push the button
|
||||
execution = new AuthenticationExecutionModel();
|
||||
execution.setParentFlow(subflow2.getId());
|
||||
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
|
||||
execution.setAuthenticator(PushButtonAuthenticatorFactory.PROVIDER_ID);
|
||||
execution.setPriority(10);
|
||||
execution.setAuthenticatorFlow(false);
|
||||
|
||||
realm.addAuthenticatorExecution(execution);
|
||||
|
||||
// Subflow2 - username-password
|
||||
execution = new AuthenticationExecutionModel();
|
||||
execution.setParentFlow(subflow2.getId());
|
||||
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
|
||||
execution.setAuthenticator(UsernamePasswordFormFactory.PROVIDER_ID);
|
||||
execution.setPriority(20);
|
||||
execution.setAuthenticatorFlow(false);
|
||||
|
||||
realm.addAuthenticatorExecution(execution);
|
||||
|
||||
client = realm.addClient(TEST_APP_FLOW);
|
||||
client.setSecret("password");
|
||||
client.setBaseUrl("http://localhost:8180/auth/realms/master/app/auth");
|
||||
client.setManagementUrl("http://localhost:8180/auth/realms/master/app/admin");
|
||||
client.setEnabled(true);
|
||||
client.addRedirectUri("http://localhost:8180/auth/realms/master/app/auth/*");
|
||||
client.setAuthenticationFlowBindingOverride(AuthenticationFlowBindings.BROWSER_BINDING, browser.getId());
|
||||
client.setPublicClient(false);
|
||||
|
||||
// Parent flow
|
||||
AuthenticationFlowModel directGrant = new AuthenticationFlowModel();
|
||||
directGrant.setAlias("direct-override-flow");
|
||||
directGrant.setDescription("direct grant based authentication");
|
||||
directGrant.setProviderId("basic-flow");
|
||||
directGrant.setTopLevel(true);
|
||||
directGrant.setBuiltIn(true);
|
||||
directGrant = realm.addAuthenticationFlow(directGrant);
|
||||
|
||||
execution = new AuthenticationExecutionModel();
|
||||
execution.setParentFlow(directGrant.getId());
|
||||
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
|
||||
execution.setAuthenticator(UsernameOnlyAuthenticator.PROVIDER_ID);
|
||||
execution.setPriority(10);
|
||||
execution.setAuthenticatorFlow(false);
|
||||
|
||||
realm.addAuthenticatorExecution(execution);
|
||||
|
||||
client = realm.addClient(TEST_APP_DIRECT_OVERRIDE);
|
||||
client.setSecret("password");
|
||||
client.setBaseUrl("http://localhost:8180/auth/realms/master/app/auth");
|
||||
client.setManagementUrl("http://localhost:8180/auth/realms/master/app/admin");
|
||||
client.setEnabled(true);
|
||||
client.addRedirectUri("http://localhost:8180/auth/realms/master/app/auth/*");
|
||||
client.setPublicClient(false);
|
||||
client.setDirectAccessGrantsEnabled(true);
|
||||
client.setAuthenticationFlowBindingOverride(AuthenticationFlowBindings.BROWSER_BINDING, browser.getId());
|
||||
client.setAuthenticationFlowBindingOverride(AuthenticationFlowBindings.DIRECT_GRANT_BINDING, directGrant.getId());
|
||||
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
//@Test
|
||||
public void testRunConsole() throws Exception {
|
||||
Thread.sleep(10000000);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testWithClientBrowserOverride() throws Exception {
|
||||
oauth.clientId(TEST_APP_FLOW);
|
||||
String loginFormUrl = oauth.getLoginFormUrl();
|
||||
log.info("loginFormUrl: " + loginFormUrl);
|
||||
|
||||
//Thread.sleep(10000000);
|
||||
|
||||
driver.navigate().to(loginFormUrl);
|
||||
|
||||
Assert.assertEquals("PushTheButton", driver.getTitle());
|
||||
|
||||
// Push the button. I am redirected to username+password form
|
||||
driver.findElement(By.name("submit1")).click();
|
||||
|
||||
|
||||
loginPage.assertCurrent();
|
||||
|
||||
// Fill username+password. I am successfully authenticated
|
||||
oauth.fillLoginForm("test-user@localhost", "password");
|
||||
appPage.assertCurrent();
|
||||
|
||||
events.expectLogin().client("test-app-flow").detail(Details.USERNAME, "test-user@localhost").assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoOverrideBrowser() throws Exception {
|
||||
String clientId = "test-app";
|
||||
testNoOverrideBrowser(clientId);
|
||||
}
|
||||
|
||||
private void testNoOverrideBrowser(String clientId) {
|
||||
oauth.clientId(clientId);
|
||||
String loginFormUrl = oauth.getLoginFormUrl();
|
||||
log.info("loginFormUrl: " + loginFormUrl);
|
||||
|
||||
//Thread.sleep(10000000);
|
||||
|
||||
driver.navigate().to(loginFormUrl);
|
||||
|
||||
loginPage.assertCurrent();
|
||||
|
||||
// Fill username+password. I am successfully authenticated
|
||||
oauth.fillLoginForm("test-user@localhost", "password");
|
||||
appPage.assertCurrent();
|
||||
|
||||
events.expectLogin().client(clientId).detail(Details.USERNAME, "test-user@localhost").assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGrantAccessTokenNoOverride() throws Exception {
|
||||
testDirectGrantNoOverride("test-app");
|
||||
}
|
||||
|
||||
private void testDirectGrantNoOverride(String clientId) {
|
||||
Client httpClient = javax.ws.rs.client.ClientBuilder.newClient();
|
||||
String grantUri = oauth.getResourceOwnerPasswordCredentialGrantUrl();
|
||||
WebTarget grantTarget = httpClient.target(grantUri);
|
||||
|
||||
{ // test no password
|
||||
String header = BasicAuthHelper.createHeader(clientId, "password");
|
||||
Form form = new Form();
|
||||
form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD);
|
||||
form.param("username", "test-user@localhost");
|
||||
Response response = grantTarget.request()
|
||||
.header(HttpHeaders.AUTHORIZATION, header)
|
||||
.post(Entity.form(form));
|
||||
assertEquals(401, response.getStatus());
|
||||
response.close();
|
||||
}
|
||||
|
||||
{ // test invalid password
|
||||
String header = BasicAuthHelper.createHeader(clientId, "password");
|
||||
Form form = new Form();
|
||||
form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD);
|
||||
form.param("username", "test-user@localhost");
|
||||
form.param("password", "invalid");
|
||||
Response response = grantTarget.request()
|
||||
.header(HttpHeaders.AUTHORIZATION, header)
|
||||
.post(Entity.form(form));
|
||||
assertEquals(401, response.getStatus());
|
||||
response.close();
|
||||
}
|
||||
|
||||
{ // test valid password
|
||||
String header = BasicAuthHelper.createHeader(clientId, "password");
|
||||
Form form = new Form();
|
||||
form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD);
|
||||
form.param("username", "test-user@localhost");
|
||||
form.param("password", "password");
|
||||
Response response = grantTarget.request()
|
||||
.header(HttpHeaders.AUTHORIZATION, header)
|
||||
.post(Entity.form(form));
|
||||
assertEquals(200, response.getStatus());
|
||||
response.close();
|
||||
}
|
||||
|
||||
httpClient.close();
|
||||
events.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGrantAccessTokenWithClientOverride() throws Exception {
|
||||
String clientId = TEST_APP_DIRECT_OVERRIDE;
|
||||
Client httpClient = javax.ws.rs.client.ClientBuilder.newClient();
|
||||
String grantUri = oauth.getResourceOwnerPasswordCredentialGrantUrl();
|
||||
WebTarget grantTarget = httpClient.target(grantUri);
|
||||
|
||||
{ // test no password
|
||||
String header = BasicAuthHelper.createHeader(clientId, "password");
|
||||
Form form = new Form();
|
||||
form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD);
|
||||
form.param("username", "test-user@localhost");
|
||||
Response response = grantTarget.request()
|
||||
.header(HttpHeaders.AUTHORIZATION, header)
|
||||
.post(Entity.form(form));
|
||||
assertEquals(200, response.getStatus());
|
||||
response.close();
|
||||
}
|
||||
|
||||
httpClient.close();
|
||||
events.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRestInterface() throws Exception {
|
||||
ClientsResource clients = adminClient.realm("test").clients();
|
||||
List<ClientRepresentation> query = clients.findByClientId(TEST_APP_DIRECT_OVERRIDE);
|
||||
ClientRepresentation clientRep = query.get(0);
|
||||
String directGrantFlowId = clientRep.getAuthenticationFlowBindingOverrides().get(AuthenticationFlowBindings.DIRECT_GRANT_BINDING);
|
||||
Assert.assertNotNull(directGrantFlowId);
|
||||
clientRep.getAuthenticationFlowBindingOverrides().put(AuthenticationFlowBindings.DIRECT_GRANT_BINDING, "");
|
||||
clients.get(clientRep.getId()).update(clientRep);
|
||||
testDirectGrantNoOverride(TEST_APP_DIRECT_OVERRIDE);
|
||||
clientRep.getAuthenticationFlowBindingOverrides().put(AuthenticationFlowBindings.DIRECT_GRANT_BINDING, directGrantFlowId);
|
||||
clients.get(clientRep.getId()).update(clientRep);
|
||||
testGrantAccessTokenWithClientOverride();
|
||||
|
||||
query = clients.findByClientId(TEST_APP_FLOW);
|
||||
clientRep = query.get(0);
|
||||
String browserFlowId = clientRep.getAuthenticationFlowBindingOverrides().get(AuthenticationFlowBindings.BROWSER_BINDING);
|
||||
Assert.assertNotNull(browserFlowId);
|
||||
clientRep.getAuthenticationFlowBindingOverrides().put(AuthenticationFlowBindings.BROWSER_BINDING, "");
|
||||
clients.get(clientRep.getId()).update(clientRep);
|
||||
testNoOverrideBrowser(TEST_APP_FLOW);
|
||||
clientRep.getAuthenticationFlowBindingOverrides().put(AuthenticationFlowBindings.BROWSER_BINDING, browserFlowId);
|
||||
clients.get(clientRep.getId()).update(clientRep);
|
||||
testWithClientBrowserOverride();
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -114,6 +114,7 @@ totpType=Type
|
|||
totpAlgorithm=Algorithm
|
||||
totpDigits=Digits
|
||||
totpInterval=Interval
|
||||
totpCounter=Counter
|
||||
|
||||
missingUsernameMessage=Please specify username.
|
||||
missingFirstNameMessage=Please specify first name.
|
||||
|
|
|
@ -49,9 +49,13 @@
|
|||
<p>${msg("totpManualStep3")}</p>
|
||||
<ul>
|
||||
<li id="kc-totp-type">${msg("totpType")}: ${msg("totp." + totp.policy.type)}</li>
|
||||
<li id="kc-totp-algorithm">${msg("totpAlgorithm")}: ${totp.policy.algorithm}</li>
|
||||
<li id="kc-totp-algorithm">${msg("totpAlgorithm")}: ${totp.policy.getAlgorithmKey()}</li>
|
||||
<li id="kc-totp-digits">${msg("totpDigits")}: ${totp.policy.digits}</li>
|
||||
<li id="kc-totp-period">${msg("totpInterval")}: ${totp.policy.period}</li>
|
||||
<#if totp.policy.type = "totp">
|
||||
<li id="kc-totp-period">${msg("totpInterval")}: ${totp.policy.period}</li>
|
||||
<#elseif totp.policy.type = "hotp">
|
||||
<li id="kc-totp-counter">${msg("totpCounter")}: ${totp.policy.initialCounter}</li>
|
||||
</#if>
|
||||
</ul>
|
||||
</li>
|
||||
<#else>
|
||||
|
|
|
@ -28,9 +28,13 @@
|
|||
<p>${msg("loginTotpManualStep3")}</p>
|
||||
<ul>
|
||||
<li id="kc-totp-type">${msg("loginTotpType")}: ${msg("loginTotp." + totp.policy.type)}</li>
|
||||
<li id="kc-totp-algorithm">${msg("loginTotpAlgorithm")}: ${totp.policy.algorithm}</li>
|
||||
<li id="kc-totp-algorithm">${msg("loginTotpAlgorithm")}: ${totp.policy.getAlgorithmKey()}</li>
|
||||
<li id="kc-totp-digits">${msg("loginTotpDigits")}: ${totp.policy.digits}</li>
|
||||
<li id="kc-totp-period">${msg("loginTotpInterval")}: ${totp.policy.period}</li>
|
||||
<#if totp.policy.type = "totp">
|
||||
<li id="kc-totp-period">${msg("loginTotpInterval")}: ${totp.policy.period}</li>
|
||||
<#elseif totp.policy.type = "hotp">
|
||||
<li id="kc-totp-counter">${msg("loginTotpCounter")}: ${totp.policy.initialCounter}</li>
|
||||
</#if>
|
||||
</ul>
|
||||
</li>
|
||||
<#else>
|
||||
|
|
|
@ -80,6 +80,7 @@ loginTotpType=Type
|
|||
loginTotpAlgorithm=Algorithm
|
||||
loginTotpDigits=Digits
|
||||
loginTotpInterval=Interval
|
||||
loginTotpCounter=Counter
|
||||
|
||||
loginTotp.totp=Time-based
|
||||
loginTotp.hotp=Counter-based
|
||||
|
|
Loading…
Reference in a new issue