keycloak-uncached

Changes

Details

diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java
index a7ca83e..77c2c45 100755
--- a/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java
@@ -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;
     }
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
index 2207153..01cfd8c 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
@@ -346,6 +346,34 @@ public class ClientAdapter implements ClientModel {
     }
 
     @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();
         return cached.getProtocolMappers();
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedClient.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedClient.java
index b4b6729..787dc45 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedClient.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedClient.java
@@ -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;
+    }
 }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
index ac1a738..3a7eabb 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
@@ -271,6 +271,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);
 
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
index dab3fe4..7f88977 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
@@ -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;
     }
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-4.0.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-4.0.0.xml
new file mode 100644
index 0000000..f7ebc68
--- /dev/null
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-4.0.0.xml
@@ -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>
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
index d89aa96..fa824e2 100755
--- a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
@@ -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>
diff --git a/server-spi/src/main/java/org/keycloak/models/AuthenticationFlowBindings.java b/server-spi/src/main/java/org/keycloak/models/AuthenticationFlowBindings.java
new file mode 100644
index 0000000..2515472
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/AuthenticationFlowBindings.java
@@ -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";
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/ClientModel.java b/server-spi/src/main/java/org/keycloak/models/ClientModel.java
index 5f4403f..aa406cf 100755
--- a/server-spi/src/main/java/org/keycloak/models/ClientModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/ClientModel.java
@@ -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);
 
diff --git a/server-spi/src/main/java/org/keycloak/models/OTPPolicy.java b/server-spi/src/main/java/org/keycloak/models/OTPPolicy.java
index 83a27fc..ca5f986 100755
--- a/server-spi/src/main/java/org/keycloak/models/OTPPolicy.java
+++ b/server-spi/src/main/java/org/keycloak/models/OTPPolicy.java
@@ -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;
     }
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/AuthenticationFlowResolver.java b/server-spi-private/src/main/java/org/keycloak/models/utils/AuthenticationFlowResolver.java
new file mode 100644
index 0000000..1a27dd8
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/AuthenticationFlowResolver.java
@@ -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();
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index aa6b42c..0dad16a 100755
--- a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -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());
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index 34ee766..5fb5003 100755
--- a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -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) {
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index 7a3c3af..4464ff7 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -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)
diff --git a/services/src/main/java/org/keycloak/connections/httpclient/DefaultHttpClientFactory.java b/services/src/main/java/org/keycloak/connections/httpclient/DefaultHttpClientFactory.java
index 217581b..7e6b4c1 100755
--- a/services/src/main/java/org/keycloak/connections/httpclient/DefaultHttpClientFactory.java
+++ b/services/src/main/java/org/keycloak/connections/httpclient/DefaultHttpClientFactory.java
@@ -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?
diff --git a/services/src/main/java/org/keycloak/connections/httpclient/HttpClientBuilder.java b/services/src/main/java/org/keycloak/connections/httpclient/HttpClientBuilder.java
index e4ac52b..e5bebf2 100755
--- a/services/src/main/java/org/keycloak/connections/httpclient/HttpClientBuilder.java
+++ b/services/src/main/java/org/keycloak/connections/httpclient/HttpClientBuilder.java
@@ -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);
diff --git a/services/src/main/java/org/keycloak/connections/httpclient/ProxyMappings.java b/services/src/main/java/org/keycloak/connections/httpclient/ProxyMappings.java
new file mode 100644
index 0000000..c534cb8
--- /dev/null
+++ b/services/src/main/java/org/keycloak/connections/httpclient/ProxyMappings.java
@@ -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 +
+        '}';
+    }
+  }
+}
diff --git a/services/src/main/java/org/keycloak/connections/httpclient/ProxyMappingsAwareRoutePlanner.java b/services/src/main/java/org/keycloak/connections/httpclient/ProxyMappingsAwareRoutePlanner.java
new file mode 100644
index 0000000..353a5bb
--- /dev/null
+++ b/services/src/main/java/org/keycloak/connections/httpclient/ProxyMappingsAwareRoutePlanner.java
@@ -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;
+  }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java b/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java
index dbed381..619a862 100755
--- a/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java
+++ b/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java
@@ -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() {
diff --git a/services/src/main/java/org/keycloak/protocol/docker/DockerEndpoint.java b/services/src/main/java/org/keycloak/protocol/docker/DockerEndpoint.java
index 0c9cb79..6ed777d 100644
--- a/services/src/main/java/org/keycloak/protocol/docker/DockerEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/docker/DockerEndpoint.java
@@ -88,7 +88,7 @@ public class DockerEndpoint extends AuthorizationEndpointBase {
     }
 
     @Override
-    protected AuthenticationFlowModel getAuthenticationFlow() {
+    protected AuthenticationFlowModel getAuthenticationFlow(AuthenticationSessionModel authSession) {
         return realm.getDockerAuthenticationFlow();
     }
 
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
index ee0ff85..3be9686 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
@@ -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)
diff --git a/services/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileService.java b/services/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileService.java
index c0be2ba..cb0c367 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileService.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileService.java
@@ -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;
diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
index 33a982f..c8e3fda 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -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)
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index f09cfbb..fd171ee 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -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) {
diff --git a/services/src/test/java/org/keycloak/connections/httpclient/ProxyMappingsTest.java b/services/src/test/java/org/keycloak/connections/httpclient/ProxyMappingsTest.java
new file mode 100644
index 0000000..3841521
--- /dev/null
+++ b/services/src/test/java/org/keycloak/connections/httpclient/ProxyMappingsTest.java
@@ -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"));
+  }
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/forms/UsernameOnlyAuthenticator.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/forms/UsernameOnlyAuthenticator.java
new file mode 100644
index 0000000..1fd72f4
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/forms/UsernameOnlyAuthenticator.java
@@ -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;
+    }
+}
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
index 18317d0..3b28d99 100755
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
@@ -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
\ No newline at end of file
+org.keycloak.testsuite.authentication.PushButtonAuthenticatorFactory
+org.keycloak.testsuite.forms.UsernameOnlyAuthenticator
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountFormServiceTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountFormServiceTest.java
index ca8a270..a942ec4 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountFormServiceTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountFormServiceTest.java
@@ -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());
 
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java
index f42a146..c2ba191 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java
@@ -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");
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java
index f55e90f..1fac71a 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java
@@ -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;
     }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/FlowOverrideTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/FlowOverrideTest.java
new file mode 100644
index 0000000..33bb259
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/FlowOverrideTest.java
@@ -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();
+
+    }
+
+}
diff --git a/themes/src/main/resources/theme/base/account/messages/messages_en.properties b/themes/src/main/resources/theme/base/account/messages/messages_en.properties
index b943edc..44f4bf2 100755
--- a/themes/src/main/resources/theme/base/account/messages/messages_en.properties
+++ b/themes/src/main/resources/theme/base/account/messages/messages_en.properties
@@ -114,6 +114,7 @@ totpType=Type
 totpAlgorithm=Algorithm
 totpDigits=Digits
 totpInterval=Interval
+totpCounter=Counter
 
 missingUsernameMessage=Please specify username.
 missingFirstNameMessage=Please specify first name.
diff --git a/themes/src/main/resources/theme/base/account/totp.ftl b/themes/src/main/resources/theme/base/account/totp.ftl
index e94eb81..8ed3fda 100755
--- a/themes/src/main/resources/theme/base/account/totp.ftl
+++ b/themes/src/main/resources/theme/base/account/totp.ftl
@@ -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>
diff --git a/themes/src/main/resources/theme/base/login/login-config-totp.ftl b/themes/src/main/resources/theme/base/login/login-config-totp.ftl
index 24383ec..bd53b27 100755
--- a/themes/src/main/resources/theme/base/login/login-config-totp.ftl
+++ b/themes/src/main/resources/theme/base/login/login-config-totp.ftl
@@ -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>
diff --git a/themes/src/main/resources/theme/base/login/messages/messages_en.properties b/themes/src/main/resources/theme/base/login/messages/messages_en.properties
index 7c5a22d..e6eb261 100755
--- a/themes/src/main/resources/theme/base/login/messages/messages_en.properties
+++ b/themes/src/main/resources/theme/base/login/messages/messages_en.properties
@@ -80,6 +80,7 @@ loginTotpType=Type
 loginTotpAlgorithm=Algorithm
 loginTotpDigits=Digits
 loginTotpInterval=Interval
+loginTotpCounter=Counter
 
 loginTotp.totp=Time-based
 loginTotp.hotp=Counter-based